Ir al contenido

Guía de Flutter para novatos - Miguel Páramos

FAQ sobre Flutter pal que no tiene ni idea de Flutter | 2º DAM · PMDM

Diferencias entre StatelessWidget y StatefulWidget , cuadro comparativo y alternativas modernas de estado

En Flutter, la interfaz de usuario se construye mediante una estructura de árbol de Widgets. La diferencia fundamental entre un StatelessWidget y un StatefulWidget radica en si el widget necesita recordar y modificar datos a lo largo del tiempo (su estado).

  • ¿Qué es?: Un widget inmutable. Una vez dibujado en pantalla con sus propiedades iniciales, no puede cambiar por sí mismo.
  • Cómo se actualiza: Solo se vuelve a renderizar si su widget padre se reconstruye y le pasa nuevos datos a través de su constructor.
  • Ciclo de vida: Prácticamente solo tiene el método build().
  • Casos de uso: Textos fijos, iconos, tarjetas informativas, botones con acciones estáticas, estructuras de maquetación (filas, columnas).
class MiTextoEstatico extends StatelessWidget { // Widget inmutable sin estado propio
final String mensaje; // Propiedad inmutable recibida desde el exterior
const MiTextoEstatico({super.key, required this.mensaje}); // Constructor constante
@override // Sobreescribe el método build
Widget build(BuildContext context) { // Retorna la descripción visual
return Text(mensaje); // Renderiza el texto inmutable en pantalla
} // Fin del método build
} // Fin de la clase MiTextoEstatico
  • ¿Qué es?: Un widget que mantiene datos que pueden cambiar dinámicamente en respuesta a la interacción del usuario o eventos asíncronos (red, temporizadores).
  • Cómo funciona: Se divide en dos clases:
    • La clase que hereda de StatefulWidget (es inmutable y define la configuración).
    • La clase State<T>, que contiene las variables mutables y el método build().
  • Mecanismo de cambio: Cuando modificas una variable interna, llamas a setState(() { ... }), lo que notifica al motor de Flutter que debe redibujar la UI con el nuevo valor.
  • Ciclo de vida propio: Incluye métodos como initState() (al crearse), didUpdateWidget() y dispose() (para liberar recursos como controladores o suscripciones).
  • Casos de uso: Contadores, campos de formulario con validación, interruptores (Switch), temporizadores, animaciones manuales.
class Contador extends StatefulWidget { // Widget con estado mutable asociado
const Contador({super.key}); // Constructor constante de la clase de configuración
@override // Crea la instancia del objeto de estado asociado
State<Contador> createState() => _ContadorState(); // Vincula con su clase de estado
} // Fin de la clase Contador
class _ContadorState extends State<Contador> { // Clase que retiene el estado dinámico
int _conteo = 0; // Variable de estado mutable que persiste en memoria
@override // Sobreescribe build para redibujar ante cambios
Widget build(BuildContext context) { // Retorna la UI actualizada según el estado
return ElevatedButton( // Botón interactivo
onPressed: () { // Callback ejecutado al presionar
setState(() { // Notifica al motor que el estado ha cambiado
_conteo++; // Incrementa el contador interno
}); // Fin de setState
}, // Fin de onPressed
child: Text('Clics: $_conteo'), // Muestra el valor reactivo actualizado
); // Fin de ElevatedButton
} // Fin del método build
} // Fin de la clase _ContadorState
Característica StatelessWidget StatefulWidget
Estado interno No tiene (inmutable) Sí tiene (objeto State)
Método setState() ❌ No existe Sí, dispara el redibujado
Consumo de memoria Muy ligero Ligeramente mayor (crea dos objetos)
Control de ciclo de vida Solo build() initState(), dispose(), etc.
Separación lógica/UI Directo en build Lógica en State, UI en build

Aunque StatefulWidget viene integrado de fábrica en Flutter, en aplicaciones medianas o grandes suele volverse complejo:

  • Requiere código repetitivo (boilerplate).
  • Puede mezclar lógica de negocio con la vista si no se tiene disciplina.
  • setState() reconstruye todo el árbol del widget en lugar de solo el nodo que cambió.

Por ello, la comunidad y el equipo de Flutter utilizan varias alternativas modernas:

A. Alternativa nativa sin librerías: ValueNotifier + ValueListenableBuilder

Te permite usar un StatelessWidget y redibujar únicamente la pequeña parte de la UI que depende de ese dato específico:

class ContadorLimpio extends StatelessWidget { // Widget sin estado que aloja un notificador reactivo
final ValueNotifier<int> _contador = ValueNotifier<int>(0); // Objeto reactivo nativo de valor único
ContadorLimpio({super.key}); // Constructor del widget
@override // Sobreescribe el método build
Widget build(BuildContext context) { // Construye la jerarquía
return ValueListenableBuilder<int>( // Escucha cambios solo en _contador
valueListenable: _contador, // Conecta con el notificador reactivo
builder: (context, valor, child) { // Redibuja ÚNICAMENTE este builder
return ElevatedButton( // Botón interactivo
onPressed: () => _contador.value++, // Incrementa el valor directamente sin setState
child: Text('Clics: $valor'), // Muestra el valor actualizado
); // Fin de ElevatedButton
}, // Fin de la función builder
); // Fin de ValueListenableBuilder
} // Fin del método build
} // Fin de la clase ContadorLimpio

B. flutter_hooks (Inspirado en React Hooks)

Sustituye StatefulWidget por un HookWidget. Elimina la necesidad de escribir dos clases, initState y dispose:

class ContadorHook extends HookWidget { // Widget de flutter_hooks sin boilerplate de State
const ContadorHook({super.key}); // Constructor constante del widget
@override // Sobreescribe el método build
Widget build(BuildContext context) { // Construye la interfaz reactiva
final contador = useState(0); // Hook de estado reactivo local en una sola línea
return ElevatedButton( // Botón interactivo
onPressed: () => contador.value++, // Muta el valor reactivo y redibuja automáticamente
child: Text('Clics: ${contador.value}'), // Muestra el conteo actual
); // Fin de ElevatedButton
} // Fin del método build
} // Fin de la clase ContadorHook

C. Gestores de Estado Modernos (Separar UI de Lógica)

Para gestionar estados compartidos entre varias pantallas o que interactúan con APIs:

  1. Riverpod (ConsumerWidget): Actualmente el estándar moderno más popular. Seguro en tiempo de compilación, no depende del árbol de widgets (BuildContext) y es muy fácil de testear.
  2. BLoC / Cubit (BlocBuilder): Basado en Streams y eventos. Es el preferido en entornos empresariales por su arquitectura estricta y separación limpia.
  3. Signals (flutter_signals): Basado en reactividad por señales (similar a Angular o SolidJS). Extremadamente rápido y con una sintaxis minimalista.
  4. Provider: El gestor clásico recomendado históricamente por el equipo de Flutter.