certmundo.
es‑mx

6 min de lectura

¿Cómo manejar el estado en una app Flutter?

El estado en Flutter es cualquier dato que puede cambiar y que afecta lo que el usuario ve en pantalla.

Cuando el usuario toca un botón, escribe en un campo o carga información nueva, la interfaz debe actualizarse. Flutter usa un mecanismo llamado setState para hacer exactamente eso.

¿Qué es el estado?

El estado es la información dinámica de tu app. Por ejemplo, el número de productos en el carrito de Liverpool, el nombre que el usuario escribió en un formulario, o si un interruptor está activado o desactivado.

Hay dos tipos principales de estado:

Tipo Descripción Ejemplo
Estado local Solo le interesa a un widget específico Un contador dentro de una pantalla
Estado global Lo comparten varios widgets El usuario que inició sesión en la app

En esta lección te enfocas en el estado local, que es el más común cuando empiezas a desarrollar.

StatelessWidget vs StatefulWidget

Antes de usar setState, necesitas entender la diferencia entre los dos tipos de widget.

StatelessWidget es un widget que no cambia. Una vez que se dibuja en pantalla, su contenido es fijo. Un texto estático o un ícono son ejemplos típicos.

StatefulWidget es un widget que puede cambiar. Tiene un objeto State asociado. Cuando llamas a setState, Flutter vuelve a dibujar ese widget con los datos nuevos.

Estructura básica de un StatefulWidget

class MiWidget extends StatefulWidget {
  @override
  State<MiWidget> createState() => _MiWidgetState();
}

class _MiWidgetState extends State<MiWidget> {
  // Variables de estado aquí

  @override
  Widget build(BuildContext context) {
    // Interfaz aquí
    return Container();
  }
}

Nota que se crean dos clases: el widget público y su estado privado (por convención, con guion bajo al inicio).

¿Cómo funciona setState?

setState es un método que le indica a Flutter que las variables de estado cambiaron y que debe redibujar el widget.

Su sintaxis es:

setState(() {
  // Cambia tus variables aquí
});

Todo lo que modifiques dentro del bloque de setState dispara la actualización visual. Si cambias una variable fuera de setState, la pantalla no se actualiza.

Ejemplo 1: Contador simple

Este ejemplo muestra un contador que sube cada vez que el usuario toca un botón. Es el ejemplo más básico del estado local.

class ContadorApp extends StatefulWidget {
  @override
  State<ContadorApp> createState() => _ContadorAppState();
}

class _ContadorAppState extends State<ContadorApp> {
  int _contador = 0;

  void _incrementar() {
    setState(() {
      _contador = _contador + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Contador FEMSA")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("Visitas registradas:"),
            Text(
              "$_contador",
              style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementar,
        child: Icon(Icons.add),
      ),
    );
  }
}

Cada vez que el usuario toca el botón +, _incrementar llama a setState y Flutter redibuja el Text con el nuevo valor de _contador.

Ejemplo 2: Formulario de captura de datos

Este ejemplo es más cercano a una app real. Imagina que construyes un formulario sencillo para capturar el nombre y salario de un empleado de Bimbo.

class FormularioEmpleado extends StatefulWidget {
  @override
  State<FormularioEmpleado> createState() => _FormularioEmpleadoState();
}

class _FormularioEmpleadoState extends State<FormularioEmpleado> {
  String _nombre = "";
  double _salario = 0;
  String _mensaje = "";

  void _mostrarResumen() {
    setState(() {
      if (_nombre.isEmpty || _salario <= 0) {
        _mensaje = "Completa todos los campos.";
      } else {
        _mensaje = "Empleado: $_nombre — Salario: \$${ _salario.toStringAsFixed(0).replaceAllMapped(
          RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
          (m) => '\${m[1]},'
        )}";
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Alta de empleado")),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              decoration: InputDecoration(labelText: "Nombre"),
              onChanged: (valor) {
                setState(() {
                  _nombre = valor;
                });
              },
            ),
            SizedBox(height: 12),
            TextField(
              decoration: InputDecoration(labelText: "Salario mensual"),
              keyboardType: TextInputType.number,
              onChanged: (valor) {
                setState(() {
                  _salario = double.tryParse(valor) ?? 0;
                });
              },
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _mostrarResumen,
              child: Text("Ver resumen"),
            ),
            SizedBox(height: 16),
            Text(
              _mensaje,
              style: TextStyle(fontSize: 16, color: Colors.indigo),
            ),
          ],
        ),
      ),
    );
  }
}

Aquí hay tres variables de estado: _nombre, _salario y _mensaje. Cada TextField actualiza su variable correspondiente con setState en el callback onChanged. Al presionar el botón, _mostrarResumen valida y genera el texto final.

Un salario típico de $18,500 para un operador de planta se mostraría como:

Empleado: Juan Pérez — Salario: $18,500

Ciclo de vida del estado

Cuando usas StatefulWidget, Flutter sigue un ciclo de vida con métodos clave:

Método ¿Cuándo se ejecuta? Uso común
initState() Una sola vez al crear el widget Inicializar variables, cargar datos
build() Cada vez que cambia el estado Dibujar la interfaz
dispose() Cuando el widget se destruye Liberar controladores y listeners

Siempre que uses un TextEditingController, llama a dispose() para evitar fugas de memoria.

final TextEditingController _ctrl = TextEditingController();

@override
void dispose() {
  _ctrl.dispose();
  super.dispose();
}

Errores comunes

1. Cambiar variables fuera de setState

// ❌ Incorrecto: la pantalla no se actualiza
_contador = _contador + 1;

// ✅ Correcto
setState(() {
  _contador = _contador + 1;
});

Si modificas una variable sin setState, el valor cambia en memoria pero Flutter no sabe que debe redibujar el widget.

2. Usar StatelessWidget cuando necesitas estado

Si ves que tu interfaz no responde a cambios, revisa que tu clase extienda StatefulWidget y no StatelessWidget. Es el error más frecuente en principiantes.

3. Poner lógica pesada dentro de setState

setState debe contener solo las asignaciones de variables. Nunca pongas llamadas a APIs, lecturas de archivos o cálculos complejos dentro del bloque. Haz esos procesos antes y guarda el resultado en una variable local.

// ❌ Incorrecto
setState(() {
  _datos = await cargarDatosDelServidor(); // error: no puedes usar await aquí
});

// ✅ Correcto
final resultado = await cargarDatosDelServidor();
setState(() {
  _datos = resultado;
});

4. No inicializar las variables de estado

Siempre da un valor inicial a tus variables. Si declaras String _nombre; sin valor, Dart lanzará un error en tiempo de compilación. Usa String _nombre = ""; o String? _nombre; si el valor puede ser nulo.

Resumen rápido

Concepto Descripción
StatefulWidget Widget que puede cambiar su contenido
State Clase que guarda las variables dinámicas
setState() Método que actualiza la interfaz
initState() Se ejecuta una vez al inicio
dispose() Limpia recursos al cerrar el widget

setState es la herramienta más directa para manejar el estado local. Para apps más grandes, existen soluciones como Provider o Riverpod, pero dominar setState es el primer paso obligatorio.

Puntos clave

  • El **estado** es cualquier dato que puede cambiar en tu app; usa `StatefulWidget` cuando tu widget necesite reflejar esos cambios en pantalla.
  • `setState` le indica a Flutter que las variables cambiaron y que debe redibujar el widget; si cambias una variable fuera de `setState`, la interfaz no se actualiza.
  • Separa la lógica pesada (llamadas a APIs, cálculos) de `setState`; el bloque solo debe contener asignaciones de variables.
  • Usa `initState()` para inicializar recursos y `dispose()` para liberarlos, especialmente cuando manejas `TextEditingController`.
  • Dominar `setState` con estado local es el paso previo antes de usar soluciones avanzadas como Provider o Riverpod en proyectos más grandes.

Comparte esta lección:

¿Cómo manejar el estado en una app Flutter? | Flutter y Dart Básico: Crea tu primera app móvil | Certmundo