Comparte el estado de la aplicación con ChangeNotifier y Provider mientras dominas watch, read y select para reconstrucciones específicas.
Abrir esta lección en KodokonsetState basta mientras el estado vive y muere con una sola pantalla. En cuanto los datos se comparten (carrito, sesión, preferencias), pasarlos de constructor en constructor se vuelve inmanejable. ChangeNotifier proporciona el observable mínimo del SDK; provider lo inyecta en el árbol y reconstruye solo los widgets suscritos. Frente a Riverpod o Bloc, esta pareja sigue siendo la más fácil de auditar: poca magia, una única dependencia, un mecanismo que puedes explicar en una sola frase.
flutter pub add providerUn notifier bien diseñado encapsula: campos privados, getters de solo lectura, mutaciones únicamente a través de métodos que terminan con notifyListeners(). Ese contrato es lo que garantiza que ningún cambio de estado se escape de la interfaz.
import 'package:flutter/foundation.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
int get count => _items.length;
void add(String item) {
_items.add(item);
notifyListeners();
}
void remove(String item) {
_items.remove(item);
notifyListeners();
}
}Tres puntos de entrada, tres usos. context.watch<T>() suscribe el widget: se reconstruye con cada notifyListeners. context.read<T>() lee sin suscribir: es la herramienta para callbacks como onPressed. context.select<T, R>() se suscribe solo a una proyección: el widget se reconstruye únicamente si ese valor concreto cambia. Regla de oro: watch y select solo en build, read en los manejadores; nunca al revés.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel extends ChangeNotifier {
int _value = 0;
int get value => _value;
void increment() {
_value++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const CounterApp(),
),
);
}
class CounterApp extends StatelessWidget {
const CounterApp({super.key});
@override
Widget build(BuildContext context) {
final value = context.watch<CounterModel>().value;
return MaterialApp(
home: Scaffold(
body: Center(child: Text('Count: $value')),
floatingActionButton: FloatingActionButton(
onPressed: () =>
context.read<CounterModel>().increment(),
child: const Icon(Icons.add),
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
int get count => _items.length;
}
class CartBadge extends StatelessWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context) {
final count = context.select(
(CartModel cart) => cart.count,
);
return Badge(
label: Text('$count'),
child: const Icon(Icons.shopping_cart),
);
}
}
onPressed, ¿cómo activas cart.add(...) sin crear una suscripción innecesaria?context.watch<CartModel>().add(...)context.read<CartModel>().add(...)context.select((CartModel c) => c.add(...))notifyListeners()?List.unmodifiable(_items) en lugar de _items directamente?notifyListeners y, por tanto, sin reconstrucción.