StatefulWidget es un widget de Flutter con estado mutable, que permite a la interfaz reaccionar a acciones del usuario, eventos asíncronos y flujos de datos. Según la documentación oficial de Flutter (Flutter.dev, 2026), StatefulWidget se utiliza para todos los elementos interactivos de la aplicación: formularios de entrada, animaciones, casillas de verificación, interruptores y pantallas que cargan datos desde la red. A diferencia de StatelessWidget, crea un objeto State separado que persiste durante todo su ciclo de vida y puede reconstruirse sin necesidad de recrear el widget en sí.
Puntos clave
StatefulWidget es una clase de Flutter que puede cambiar su estado en respuesta a acciones del usuario, eventos del sistema u operaciones asíncronas. A diferencia de StatelessWidget, StatefulWidget no se renderiza directamente — crea un objeto State que se encarga del renderizado. Esta separación en dos clases (Widget y State) permite a Flutter reconstruir la interfaz sin recrear el widget en sí, lo que proporciona una ventaja significativa de rendimiento durante actualizaciones frecuentes.
La arquitectura de StatefulWidget sigue el patrón de “separación de lo mutable y lo inmutable”: el widget en sí permanece inmutable (como StatelessWidget), mientras que todo el estado mutable se almacena en un objeto State separado. Esto permite a Flutter reutilizar widgets comparándolos por tipo y Key, preservando al mismo tiempo el estado real entre reconstrucciones.
Según Google (Flutter Architectural Overview, 2026), StatefulWidget es óptimo para escenarios donde el estado cambia más de una vez durante la vida del widget: campos de texto, animaciones, temporizadores, flujos de datos, cargas asíncronas. Para una inicialización única, StatelessWidget es suficiente.
StatefulWidget es obligatorio cuando el widget debe responder a eventos externos: clics de botón, finalización de solicitudes HTTP, actualizaciones de datos de la base de datos, suscripciones a WebSocket. También es necesario para widgets con animaciones, campos de texto con controladores y componentes que gestionan el foco. Si un widget solo muestra datos y no genera eventos, usa StatelessWidget.
StatefulWidget consta de dos clases: el propio StatefulWidget (ligero, inmutable) y State (pesado, mutable). El framework crea State mediante el método createState(), llamado una vez al insertarse en el árbol. State recibe una referencia al widget a través de la propiedad widget y puede acceder a sus campos en cualquier momento del ciclo de vida.
El ciclo de vida de StatefulWidget consta de seis etapas principales, cada una de las cuales proporciona un método redefinible para realizar tareas específicas. Comprender estas etapas es fundamental para la gestión adecuada de recursos y evitar fugas de memoria.
createState es el primer método del ciclo de vida, llamado cuando StatefulWidget se inserta en el árbol. Debe devolver una nueva instancia de State asociada a este widget. Este método se llama exactamente una vez durante toda la vida del elemento. Es importante no realizar operaciones pesadas aquí — createState debe ser lo más ligero posible.
initState se llama inmediatamente después de la creación de State, antes de la primera construcción de la interfaz. Aquí se realiza: inicialización de controladores (TextEditingController, AnimationController), suscripción a flujos de datos (StreamSubscription), configuración de temporizadores e inicialización de campos. Según la documentación de Flutter (Flutter.dev, 2026), no se puede llamar a BuildContext.of() en initState — el árbol aún no está completamente montado.
didChangeDependencies se llama después de initState y cada vez que cambian las dependencias de InheritedWidget. Este es un lugar adecuado para llamar a MediaQuery.of(context) o suscribirse a Theme — valores que pueden cambiar durante la ejecución de la aplicación. Si un widget usa InheritedWidget, la lógica de inicialización debe estar aquí, no en initState.
build es el método principal que devuelve el árbol de widgets. Se llama después de initState, después de didChangeDependencies y después de cada setState. didUpdateWidget se llama cuando el padre se reconstruye y pasa un StatefulWidget con nuevos parámetros. Aquí se pueden comparar los campos antiguos y nuevos del widget y, si es necesario, actualizar el estado.
dispose es la etapa final del ciclo de vida. Aquí se liberan todos los recursos: se cancelan las suscripciones a flujos, se eliminan los controladores, se cancelan los temporizadores. No llamar a dispose provoca fugas de memoria. Después de dispose, State se considera muerto — llamar a setState dentro de él lanza una excepción.
El mecanismo de funcionamiento de StatefulWidget se basa en el trabajo coordinado de tres entidades: Widget (descripción ligera), Element (capa intermedia) y State (almacenamiento de datos). Cuando Flutter encuentra un StatefulWidget en la descripción, crea un StatefulElement, que llama a createState y almacena una referencia al objeto State. Cuando el padre se reconstruye, Flutter compara el nuevo widget con el Element actual — si el tipo y la Key coinciden, el Element se actualiza y el State permanece igual.
El estado solo se cambia mediante la llamada a setState, que notifica al framework que se necesita una reconstrucción. Es importante entender: setState no cambia el estado automáticamente — solo marca el widget como “sucio”. El desarrollador actualiza independientemente los campos de State en el callback pasado a setState. Después de que el callback se completa, Flutter llama a build y actualiza la interfaz.
Según el equipo de Dart/Flutter (Dart Language Specification, 2026), esta separación garantiza que todos los cambios de estado ocurran sincrónicamente antes de que se llame a build, eliminando la situación en la que la interfaz muestra datos parcialmente actualizados. Este es un mecanismo clave de consistencia de la interfaz en Flutter.
Veamos un StatefulWidget simple — un contador de clics en un botón. Demuestra el patrón básico: creación de State, inicialización de un campo en initState, cambio mediante setState:
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Incrementar'),
),
],
);
}
}
Un ejemplo con carga asíncrona de datos y gestión del ciclo de vida. StatefulWidget carga datos desde la red y muestra el estado de carga:
class UserProfilePage extends StatefulWidget {
final String userId;
const UserProfilePage({super.key, required this.userId});
@override
State<UserProfilePage> createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
UserModel? _user;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await UserService.fetchUser(widget.userId);
setState(() {
_user = user;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
if (_isLoading) return const CircularProgressIndicator();
return Text('Hola, ${_user!.name}');
}
}
En el segundo ejemplo, es importante señalar: initState inicia una operación asíncrona, pero el método en sí no es asíncrono. La asincronía se implementa mediante async/await dentro de un método separado _loadUser, que actualiza el estado mediante setState después de que se completa la solicitud. Este enfoque garantiza que el widget muestre correctamente el indicador de carga antes de recibir los datos.
La elección entre StatefulWidget y StatelessWidget no se trata solo de tener estado. StatefulWidget proporciona un ciclo de vida completo con los métodos initState, didChangeDependencies, didUpdateWidget y dispose, que son necesarios para trabajar con controladores, animaciones y flujos. StatelessWidget, por su parte, no tiene estos métodos y siempre es más ligero para el framework.
La recomendación del equipo de Flutter (Flutter docs, 2026) es minimizar la cantidad de StatefulWidgets en una aplicación, elevando el estado hacia arriba en el árbol (State Hoisting) o utilizando soluciones de gestión de estado (Riverpod, Bloc, Provider). Cada StatefulWidget crea un objeto State que vive hasta que se elimina el elemento — cuantos más widgets de este tipo, mayor es la carga de memoria.
| Criterio | StatefulWidget | StatelessWidget |
|---|---|---|
| Estado | Mutable | Inmutable |
| Ciclo de vida | 6 etapas | Solo build |
| Objeto State | Se crea por separado | No se requiere |
| setState | Disponible | No disponible |
| Suscripciones | initState/dispose | No compatibles |
| Constructor const | Limitado | Totalmente compatible |
| Consumo de memoria | Mayor | Menor |
StatefulWidget requiere más recursos que StatelessWidget debido a la necesidad de crear y mantener un objeto State. Sin embargo, el uso correcto de StatefulWidget no provoca problemas de rendimiento si se siguen algunas reglas. Primero, evita el anidamiento profundo de StatefulWidget — cada nivel añade sobrecarga al recorrido del árbol. Segundo, divide un StatefulWidget complejo en varios simples, cada uno responsable de su propia parte del estado.
Según la investigación de rendimiento de Flutter (Flutter.dev, febrero de 2026), la causa más común de caídas de FPS es llamar a setState en un widget padre que reconstruye todos los descendientes, incluidos StatelessWidgets que no han cambiado su visualización. La solución es extraer la parte mutable de la interfaz en un StatefulWidget separado para que setState solo reconstruya los widgets mínimos necesarios.
Usar const dentro de State es otra técnica importante. Si los widgets hijos se declaran como const, Flutter no los reconstruirá cuando se llame a setState en el padre. Esto reduce la carga del framework y disminuye el tiempo de renderizado del fotograma.
Cada llamada a setState desencadena una reconstrucción completa del widget. Si el estado cambia con alta frecuencia (por ejemplo, animación o flujo de datos), considera usar AnimatedBuilder, ValueListenableBuilder o StreamBuilder en lugar de llamar a setState manualmente. Estos widgets optimizan la reconstrucción, actualizando solo la parte de la interfaz que realmente ha cambiado.
El primer error común con StatefulWidget es llamar a setState después de dispose. Cuando un widget se elimina del árbol, State se considera muerto, y cualquier llamada a setState lanza una excepción “setState called after dispose”. Esto ocurre más a menudo cuando una operación asíncrona finaliza después de que el widget ha sido eliminado. La solución es verificar la bandera mounted antes de llamar a setState o cancelar las operaciones asíncronas en dispose.
El segundo error es realizar cálculos pesados en el método build. Dado que build se llama en cada setState y en cada reconstrucción del padre, todos los cálculos deben ser lo más ligeros posible. Si se necesita una operación intensiva en recursos, muévela a un Isolate separado o almacena en caché el resultado en un campo de State.
El tercer error es no llamar a super.initState() y super.dispose(). Al redefinir estos métodos, el desarrollador debe llamar a la implementación del padre. De lo contrario, el framework no podrá gestionar correctamente el estado de Element, lo que provocará errores difíciles de rastrear.
mounted antes de setState en callbacks asíncronossuper.initState() y super.dispose()Preguntas frecuentes
StatefulWidget puede cambiar su estado mediante setState, tiene un ciclo de vida (initState, dispose) y crea un objeto State separado. StatelessWidget no puede cambiar el estado y no tiene métodos de ciclo de vida — simplemente muestra los datos que recibe.
createState se llama exactamente una vez por cada instancia de StatefulElement. Incluso si el padre se reconstruye múltiples veces, mientras el tipo y la Key del widget no cambien, createState no se llama — se utiliza el objeto State existente.
Los recursos no se liberarán: los controladores seguirán funcionando en segundo plano, las suscripciones a flujos permanecerán activas, los temporizadores no se cancelarán. Esto provoca fugas de memoria y puede causar llamadas a setState después de dispose, lo que lanza una excepción.
Sí, el constructor de StatefulWidget puede ser const. Sin embargo, esto no proporciona el mismo beneficio que para StatelessWidget — el objeto State se creará igualmente en la primera inserción. const solo afecta al widget en sí (la envoltura ligera), no al State.
didUpdateWidget se llama cuando el padre pasa un StatefulWidget con nuevos parámetros. Esto es necesario para sincronizar el estado con los nuevos datos — por ejemplo, si userId en los parámetros ha cambiado, se debe cargar el perfil del nuevo usuario.
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