flutter-provider-consumo-optimizado

Consumir Provider con watch, read, select y Consumer

  • 4 min

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)),
  );
}
Copied!

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 onPressed o onTap.
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"),
)
Copied!

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);
}
Copied!

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
          ],
        );
      },
    ),
  ),
)
Copied!

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étodoSintaxis¿Reconstruye?¿Cuándo usarlo?
Watchcontext.watch<T>()✅ SÍ (Todo)Cuando necesitas escuchar todo el proveedor.
Readcontext.read<T>()❌ NODentro de funciones onPressed o onTap.
Selectcontext.select<T, R>(fn)✅ SÍ (Selectivo)Cuando solo te interesa un campo (provider.edad).
ConsumerConsumer<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.