El consumo de estado es la forma en la que un widget lee datos compartidos.
En el artículo anterior vimos cómo conectar una pantalla a un Provider usando context.watch. Funcionaba, pero tenía un “efecto secundario” peligroso.
Si pones un context.watch al principio del método build, cualquier llamada a notifyListeners hará que ese widget vuelva a ejecutar build.
Si tu pantalla es simple, no pasa nada. Pero si tienes una lista de 100 elementos, animaciones o imágenes pesadas, reconstruirlo todo porque cambió un simple contador es un desperdicio de batería y CPU.
Vamos a aprender a reconstruir la UI selectivamente.
Escuchar con context.watch<T>()
Es la forma más directa. Escucha al proveedor completo.
- Comportamiento: Si el proveedor llama a
notifyListeners(), el widget donde está esta línea se reconstruye. - Uso: Ideal cuando el widget es pequeño y todo su contenido depende de esos datos.
@override
Widget build(BuildContext context) {
// ⚠️ CUIDADO: Si UserProvider cambia, TODO este build se ejecuta de nuevo
final user = context.watch<UserProvider>();
return Scaffold(
body: Center(child: Text(user.name)),
);
}
Acceder sin escuchar con context.read<T>()
Es lo opuesto a watch. Obtiene el proveedor, pero cierra los oídos.
- Comportamiento: Accede a los métodos y propiedades, pero nunca provoca una reconstrucción, aunque los datos cambien.
- Uso: Obligatorio dentro de eventos como
onPressedoonTap.
ElevatedButton(
onPressed: () {
// ✅ CORRECTO: Usamos read para ejecutar acciones
context.read<UserProvider>().cambiarNombre("Luis");
// ❌ ERROR: Nunca uses watch aquí, dará error en tiempo de ejecución
// context.watch<UserProvider>()...
},
child: Text("Cambiar"),
)
Seleccionar un valor con context.select<T, R>()
Ahora afinamos un poco más.
Imagina que tu UserProvider tiene name (que cambia poco) y age (que cambia mucho). Si usas watch, tu widget se repintará cuando cambie la edad, aunque solo estés mostrando el nombre.
select te permite escuchar solo una propiedad específica.
@override
Widget build(BuildContext context) {
// 🪄 MAGIA: Solo se reconstruye si cambia el 'name'.
// Si cambia 'age', este widget ignora el cambio.
final nombre = context.select<UserProvider, String>((provider) => provider.name);
return Text(nombre);
}
Es la herramienta más potente para optimizar pantallas complejas sin dividir todo en widgets pequeños.
Delimitar con Consumer<T>
El widget Consumer es una alternativa a context.watch que nos permite aislar visualmente qué parte del árbol se reconstruye.
Es útil cuando tienes un widget padre muy costoso de construir (con animaciones o gradientes complejos) y quieres cambiar solo un textito pequeño que hay dentro.
El parámetro child
Fíjate que Consumer tiene un parámetro child en su constructor que a su vez se pasa al builder. Esto confunde a mucha gente, pero es genial.
Ese child es la parte estática que NO queremos reconstruir.
Scaffold(
body: Center(
child: Consumer<UserProvider>(
// 1. Definimos lo que NO cambia (El Child estático)
child: Image.asset('assets/logo_pesado.png'),
// 2. El Builder recibe ese 'child' ya construido
builder: (context, provider, child) {
print("Reconstruyendo solo el texto...");
return Column(
children: [
child!, // El builder no vuelve a construir esta instancia
Text("Hola ${provider.name}"), // Esto sí cambia
],
);
},
),
),
)
En este ejemplo, el builder recibe la misma instancia de la imagen y no vuelve a construirla cuando cambia el nombre. Eso no significa que nunca participe en layout o pintura, porque esas fases dependen del resto del árbol.
Comparativa
| Método | Sintaxis | ¿Reconstruye? | ¿Cuándo usarlo? |
|---|---|---|---|
| Watch | context.watch<T>() | ✅ SÍ (Todo) | Cuando necesitas escuchar todo el proveedor. |
| Read | context.read<T>() | ❌ NO | Dentro de funciones onPressed o onTap. |
| Select | context.select<T, R>(fn) | ✅ SÍ (Selectivo) | Cuando solo te interesa un campo (provider.edad). |
| Consumer | Consumer<T>(...) | ✅ SÍ (Aislado) | Cuando quieres proteger al padre de reconstrucciones o usar el argumento child para optimizar. |
Qué opción elegir
context.watch y context.select mantienen el código plano. Consumer resulta útil cuando quieres acotar visualmente la dependencia o aprovechar su argumento child. Elige la opción que haga más evidente qué dato escucha cada widget.