flutter-provider-basico-changenotifier

Gestión de estado con Provider y ChangeNotifier

  • 3 min

Provider es un paquete para compartir estado entre widgets.

Provider funciona como una “antena de radio”.

  1. Tienes una Emisora (tu clase con los datos).
  2. Tienes una Torre de transmisión (que inyecta la señal en la App).
  3. Tienes Radios (los widgets que escuchan la señal y reaccionan).

Vamos a montar este sistema paso a paso.

Instalar Provider

Añade el paquete a tu pubspec.yaml:

flutter pub add provider
Copied!

Crear un ChangeNotifier

Primero, necesitamos una clase que guarde nuestros datos. No es un Widget, es una clase pura de Dart que extiende de ChangeNotifier.

Esta clase tiene dos funcionalidades:

  1. Guardar datos privados.
  2. Llamar al método notifyListeners().

Este método es la “campanilla”. Cuando lo ejecutas, le gritas a toda la App: “¡Oigan, he cambiado! ¡El que me esté escuchando, que se redibuje!”.

Crea un archivo providers/counter_provider.dart:

import 'package:flutter/foundation.dart';

class CounterProvider extends ChangeNotifier {
  // 1. El Estado (Privado para que nadie lo toque directamente)
  int _count = 0;

  // 2. Getter para leer el estado
  int get count => _count;

  // 3. Método para modificar el estado
  void increment() {
    _count++;
    
    // 🔔 ¡LA CLAVE! Avisamos a los oyentes
    notifyListeners(); 
  }
}
Copied!

Si olvidas poner notifyListeners(), la variable _count cambiará, pero la pantalla no se actualizará. Es el error más común.

Inyectar el estado con ChangeNotifierProvider

Ahora tenemos la clase, pero la App no sabe que existe. Tenemos que “inyectarla” en el árbol de widgets.

En este ejemplo queremos que el contador esté disponible en toda la aplicación, así que envolvemos MaterialApp con un ChangeNotifierProvider. En otros casos conviene colocarlo más abajo para limitar su alcance.

En tu main.dart:

import 'package:provider/provider.dart';
import 'providers/counter_provider.dart';

void main() {
  runApp(
    // Inyectamos el proveedor en la cima del árbol
    ChangeNotifierProvider(
      create: (_) => CounterProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget { ... }
Copied!

Ahora, cualquier widget dentro de MyApp tiene acceso al CounterProvider.

Consumir con context.watch y context.read

Ahora ocurre el cambio interesante. Ya no recibimos datos por el constructor. Simplemente los pedimos al árbol de widgets usando el context.

Tenemos dos formas de pedir datos, y conviene saber bien la diferencia:

A. context.watch<T>() (El Observador)

Se usa dentro del método build. Significa: “Dame el dato y, si cambia, redibújame (haz un setState automático)”.

B. context.read<T>() (El Ejecutor)

Se usa dentro de eventos (onPressed, onTap). Significa: “Dame acceso a la clase para ejecutar una función, pero no me redibujes si algo cambia”.

class PantallaContador extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Dibujando Pantalla..."); // Para depurar

    // 1. ESCUCHAR (Watch): Si el contador cambia, este build se ejecuta de nuevo
    final contadorProvider = context.watch<CounterProvider>();

    return Scaffold(
      appBar: AppBar(title: Text("Ejemplo Provider")),
      body: Center(
        child: Text(
          'Valor: ${contadorProvider.count}', // Leemos el valor
          style: TextStyle(fontSize: 40),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 2. EJECUTAR (Read): Solo queremos llamar a la función
          // No uses .watch aquí, o darás error.
          context.read<CounterProvider>().increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
Copied!

Delimitar la reconstrucción con Consumer

A veces, usar context.watch al principio de build reconstruye un widget más grande de lo necesario.

Si quieres redibujar solo un trocito (por ejemplo, solo el Texto del número y no el resto de la pantalla), puedes usar el widget Consumer.

// En este ejemplo, el Scaffold NO se redibuja, solo el Text
Scaffold(
  body: Center(
    child: Consumer<CounterProvider>(
      builder: (context, provider, child) {
        return Text('Valor: ${provider.count}');
      },
    ),
  ),
  // ...
)
Copied!

Para empezar, context.watch suele ser más directo. Usa Consumer cuando quieras delimitar con claridad qué fragmento depende del proveedor o reutilizar su argumento child.