← Flutter & Dart

Fundamentos de Flutter

¿Cuáles son los métodos del ciclo de vida de un `State` y para qué sirve cada uno?

Ver respuesta — intenta responderla en voz alta primero

Los principales son: initState (se llama una vez al crear el estado), didChangeDependencies (cuando cambian dependencias heredadas), build (cada vez que hay que dibujar), didUpdateWidget (cuando el widget se reconfigura), setState (para pedir una reconstrucción) y dispose (una vez al destruir, para liberar recursos).

El orden típico de vida de un State:

  1. initState(): se ejecuta una sola vez, al insertarse en el árbol. Aquí inicializas controladores, suscripciones o peticiones iniciales. No puedes usar context para InheritedWidget de forma segura todavía.
  2. didChangeDependencies(): justo después de initState y cada vez que cambia un InheritedWidget del que dependes. Buen lugar para leer un Provider que puede cambiar.
  3. build(): construye la UI. Se llama muchas veces; debe ser rápido y sin efectos secundarios.
  4. didUpdateWidget(old): cuando el widget padre se reconstruye y pasa una nueva configuración. Aquí comparas con oldWidget para reaccionar a cambios de parámetros.
  5. setState(): marca el estado como "sucio" y agenda un build.
  6. dispose(): se ejecuta una vez, al retirar el widget del árbol para siempre. Aquí liberas recursos: cancelas streams, dispose() de controladores, etc.
import 'package:flutter/material.dart';

class Reloj extends StatefulWidget {
  const Reloj({super.key});
  @override
  State<Reloj> createState() => _RelojState();
}

class _RelojState extends State<Reloj> {
  late final TextEditingController _controlador;

  @override
  void initState() {
    super.initState();
    _controlador = TextEditingController(); // inicializo recursos
    print("initState: solo una vez");
  }

  @override
  void didUpdateWidget(covariant Reloj oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("El widget se reconfiguró");
  }

  @override
  void dispose() {
    _controlador.dispose(); // libero recursos SIEMPRE
    print("dispose: limpieza final");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(controller: _controlador);
  }
}

No liberar recursos en dispose(). Si creas un TextEditingController, un AnimationController, un Timer o una suscripción a un Stream y no los liberas, generas fugas de memoria y advertencias en consola. Regla: todo lo que inicializas en initState (o que tenga dispose) debe limpiarse en dispose.

"El ciclo empieza con initState, que corre una sola vez y donde inicializo controladores y suscripciones. Luego didChangeDependencies cuando cambian dependencias heredadas. build se llama muchas veces para dibujar y debe ser puro y rápido. didUpdateWidget corre cuando el padre me pasa una configuración nueva. setState agenda una reconstrucción. Y dispose corre una vez al final, donde libero todo lo que abrí para evitar fugas de memoria. La regla clave es que todo lo que abro en initState lo cierro en dispose."

Reto rápido

¿Cuántas veces se ejecuta initState durante la vida de un State?

Ver respuesta

Exactamente una vez, cuando el State se inserta en el árbol por primera vez. Aunque el widget se reconstruya muchas veces (llamando a build repetidamente), initState no se vuelve a ejecutar. Su contraparte, dispose, también corre una sola vez, al retirar el widget del árbol.