Provider es un paquete para compartir estado entre widgets.
Provider funciona como una “antena de radio”.
- Tienes una Emisora (tu clase con los datos).
- Tienes una Torre de transmisión (que inyecta la señal en la App).
- 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
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:
- Guardar datos privados.
- 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();
}
}
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 { ... }
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),
),
);
}
}
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}');
},
),
),
// ...
)
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.