State es el objeto central de gestión de datos en Flutter, asociado con StatefulWidget y responsable de almacenar información mutable y construir la interfaz. Según la documentación oficial de Flutter (Flutter.dev, 2026), State existe durante todo el ciclo de vida del widget y sobrevive a sus reconstrucciones, garantizando la consistencia de los datos entre las actualizaciones de la UI. A diferencia del widget en sí, State puede modificar sus campos e iniciar la reconstrucción mediante la llamada a setState.
Puntos clave
State es un objeto en la arquitectura de Flutter que almacena datos mutables de un StatefulWidget y determina cómo se muestran estos datos en la interfaz. Cada StatefulWidget, al insertarse en el árbol, crea exactamente un objeto State mediante el método createState. State existe independientemente del widget: si el padre reconstruye el StatefulWidget con nuevos parámetros, State permanece igual y recibe el widget actualizado a través de la propiedad widget.
Según la Visión General Arquitectónica de Flutter (Google, 2026), la separación de Widget y State es una decisión arquitectónica deliberada que permite al framework reutilizar elementos del árbol. El widget (una descripción ligera) puede crearse y destruirse varias veces, pero State (un objeto pesado con datos) permanece en la memoria mientras el elemento esté en el árbol. Esto evita la pérdida de datos durante reconstrucciones frecuentes de los widgets padre.
State implementa la interfaz StatefulWidget mediante genéricos: class _MyState extends State<MyWidget>. El genérico vincula State con un tipo específico de StatefulWidget, proporcionando acceso con seguridad de tipos a sus campos a través de la propiedad widget.
El objeto State se almacena en StatefulElement — una capa intermedia entre Widget y RenderObject. StatefulElement crea State mediante createState, guarda una referencia a él y pasa State como propietario. El Element solo se destruye cuando el widget se elimina del árbol — hasta entonces, State vive en la memoria.
El ciclo de vida de State es determinista y consiste en una secuencia estricta de llamadas. Comprender esta secuencia es la base para una gestión correcta de recursos y la prevención de fugas de memoria.
initState se llama primero al crear State. En este método se inicializan controladores, suscripciones a flujos, temporizadores y valores iniciales de campos. Es obligatorio llamar a super.initState() en la primera línea. En la etapa de initState, el árbol de widgets aún no está completamente montado, por lo que métodos como MediaQuery.of(context) pueden no funcionar correctamente.
didChangeDependencies se llama después de initState y cada vez que cambian las dependencias de InheritedWidget. Aquí, y no en initState, es donde se debe llamar a MediaQuery.of(context) o Theme.of(context), ya que para entonces el árbol ya está montado. Este método también se llama si el widget se mueve a un contexto diferente donde InheritedWidget proporciona otros valores.
build es el método principal de State que devuelve un árbol de widgets. Se llama después de initState, después de didChangeDependencies y después de cada setState. El método build no debe tener efectos secundarios — solo describe la interfaz basada en los valores actuales de los campos de State.
didUpdateWidget se llama cuando el padre reconstruye el StatefulWidget con nuevos parámetros. State obtiene acceso al widget antiguo a través de oldWidget y puede compararlo con el nuevo. Si los parámetros han cambiado, se puede actualizar el estado, cargar nuevos datos o reiniciar una animación.
dispose es el método final donde se liberan todos los recursos: controladores, suscripciones, temporizadores. Después de dispose, State se marca como muerto: mounted devuelve false, llamar a setState lanza una excepción. Es obligatorio llamar a super.dispose() en la última línea del método.
| Método | Cuándo se llama | super obligatorio |
|---|---|---|
| initState | Al crear State | Sí, en la primera línea |
| didChangeDependencies | Después de initState y al cambiar InheritedWidget | Sí |
| build | Después de initState, didChangeDependencies, setState | No |
| didUpdateWidget | Al recibir un nuevo widget del padre | Sí |
| setState | Por llamada del desarrollador | No |
| dispose | Al eliminar del árbol | Sí, en la última línea |
El mecanismo de funcionamiento de State se basa en tres principios clave: asociación con Element, reactividad mediante setState y acceso al padre a través de la propiedad widget. Cuando Flutter construye el árbol de elementos y encuentra un StatefulElement, llama a createState del widget asociado. El State creado se almacena en el elemento y existe hasta que el elemento se elimina.
Al llamar a setState, State se marca como sucio y programa una reconstrucción para el siguiente fotograma. Es importante: setState no llama a build inmediatamente — solo registra la necesidad de reconstrucción. Flutter recoge todos los elementos sucios del fotograma actual y los reconstruye en lote, lo que optimiza el rendimiento. Después de la llamada a build, State vuelve al estado limpio.
La propiedad widget permite a State leer los parámetros pasados al constructor de StatefulWidget. Dado que StatefulWidget es inmutable (como StatelessWidget), sus campos no cambian — cuando los parámetros cambian, el padre crea un nuevo widget y State lo recibe a través de didUpdateWidget. Esto garantiza que State siempre trabaje con los datos actuales del padre.
Ejemplo básico de State con un campo modificado por un temporizador. Demuestra initState, setState y dispose:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
Ejemplo que usa la propiedad widget para acceder a los parámetros del padre y reaccionar a sus cambios mediante didUpdateWidget:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
En el segundo ejemplo, State rastrea los cambios del parámetro de entrada name y reformatea la visualización solo cuando ocurre un cambio real. Sin la comprobación widget.name != oldWidget.name, el método se llamaría en cada reconstrucción del padre, incluso si el nombre no cambió — trabajo innecesario para el framework.
State y StatefulWidget son dos clases diferentes en la arquitectura de Flutter que cumplen roles distintos. StatefulWidget es un envoltorio inmutable ligero que describe la configuración del widget y crea State. State es un objeto pesado que almacena datos mutables, gestiona suscripciones y construye la UI. Esta separación permite a Flutter destruir y crear widgets sin perder el estado.
Todos los campos de StatefulWidget deben ser final y establecerse en el constructor — no cambian después de la creación. State, por otro lado, puede modificar sus campos en cualquier momento, pero todos los cambios deben ser precedidos por una llamada a setState para que Flutter sepa de la necesidad de reconstrucción. Esta es la diferencia clave: StatefulWidget es “qué mostrar”, State es “cómo mostrar y qué datos usar”.
Según el análisis del código fuente de Flutter (Flutter SDK, 2026), StatefulWidget contiene solo un campo obligatorio — createState, mientras que State tiene acceso a BuildContext, puede suscribirse a flujos, gestionar animaciones y controladores. Se recomienda mantener StatefulWidget lo más simple posible, trasladando toda la lógica a State.
La separación de Widget y State es una decisión arquitectónica que garantiza la inmutabilidad de la configuración. Si StatefulWidget mismo almacenara el estado, el estado se perdería en cada reconstrucción del padre. Al mover el estado a un objeto separado, Flutter garantiza que los datos sobrevivan a las reconstrucciones, mientras que los widgets siguen siendo ligeros y comparables.
El objeto State está aislado — no tiene acceso directo al State de otros widgets. Para el intercambio de datos entre widgets se utilizan InheritedWidget o herramientas externas de gestión de estado: Provider, Riverpod, Bloc, Redux. Cada enfoque resuelve el problema de manera diferente: InheritedWidget funciona a través del árbol de widgets, Provider a través de un contenedor DI, Bloc a través de flujos de eventos.
La elección de la herramienta depende de la escala del proyecto. Para una aplicación pequeña, InheritedWidget y State local son suficientes. Para proyectos medianos y grandes, se recomienda Riverpod o Bloc — garantizan testabilidad, previsibilidad y separación de la lógica de la UI. State se usa entonces solo para datos locales del widget (foco, scroll, animación).
Según la Encuesta de la Comunidad Flutter 2025 (Flutter Foundation, diciembre de 2025), Riverpod es la solución de gestión de estado más popular en proyectos nuevos (38%), seguido de Bloc (31%) y Provider (22%). Las tres herramientas son compatibles con State y no requieren abandonar el ciclo de vida estándar.
El primer error es olvidar verificar mounted antes de setState en una devolución de llamada asíncrona. Cuando un widget se elimina del árbol (por ejemplo, el usuario navegó fuera), pero una operación asíncrona (solicitud HTTP) todavía se está ejecutando, después de su finalización State ya está muerto. Llamar a setState en un State muerto lanza una excepción. La comprobación if (mounted) setState(...) resuelve el problema.
El segundo error es inicializar dependencias de InheritedWidget en initState en lugar de didChangeDependencies. En initState, el contexto aún no está montado, por lo que MediaQuery.of(context) lanzará una excepción. Todas las dependencias de InheritedWidget deben configurarse en didChangeDependencies o en build.
El tercer error es mutar campos sin llamar a setState. Si un desarrollador cambia un campo de State sin setState, Flutter no se enterará del cambio y la UI no se actualizará. Por ejemplo: _list.add(item) sin un setState((){}) posterior modificará la lista, pero la pantalla permanecerá igual.
Patrón de seguridad para operaciones asíncronas en State:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
Verificar mounted garantiza que setState solo se llame en un State vivo, evitando la excepción “setState called after dispose”.
Preguntas frecuentes
StatefulWidget es una configuración de widget inmutable, mientras que State es un objeto mutable que almacena datos y gestiona el ciclo de vida. El widget puede ser recreado, State no. StatefulWidget crea State mediante createState.
Exactamente uno. El método createState se llama una vez cuando el StatefulWidget se inserta por primera vez en el árbol. Incluso si el padre se reconstruye varias veces, el objeto State sigue siendo el mismo hasta que cambie el tipo o la Key del widget.
mounted es un indicador booleano que muestra si State está en el árbol de widgets. Después de llamar a dispose, mounted se vuelve false. Se usa para verificar antes de setState en devoluciones de llamada asíncronas para evitar excepciones.
No. State siempre está vinculado a un StatefulWidget específico mediante genéricos: State<T extends StatefulWidget>. Crear State directamente, sin asociación con un widget, es arquitectónicamente imposible.
Se lanza una excepción: “setState called after dispose”. Después de dispose, State se considera muerto y cualquier intento de reconstruir la UI mediante setState está prohibido. La solución es verificar mounted antes de cada setState.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también