Дели состояние приложения через ChangeNotifier и Provider, уверенно владея watch, read и select для точечных перерисовок.
Открыть этот урок в KodokonsetState хватает, пока состояние живёт и умирает вместе с одним экраном. Как только данные становятся общими - корзина, сессия, настройки - передавать их из конструктора в конструктор становится невозможно. ChangeNotifier даёт минимальный наблюдаемый объект из SDK; provider внедряет его в дерево и перерисовывает только подписанные виджеты. По сравнению с Riverpod или Bloc эта пара остаётся самой простой для аудита: мало магии, одна зависимость, механизм, который объясняется одной фразой.
flutter pub add providerХорошо спроектированный notifier инкапсулирует: приватные поля, геттеры только для чтения, изменения только через методы, которые заканчиваются вызовом notifyListeners(). Именно этот контракт гарантирует, что ни одно изменение состояния не проскочит мимо интерфейса.
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();
}
}Три точки входа, три применения. context.watch<T>() подписывает виджет: он перерисовывается при каждом notifyListeners. context.read<T>() читает без подписки: это инструмент для колбэков вроде onPressed. context.select<T, R>() подписывается только на проекцию: виджет перерисовывается, только если меняется именно это значение. Золотое правило: watch и select - только в build, read - в обработчиках, и никогда наоборот.
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, как вызвать cart.add(...), не создавая лишней подписки?context.watch<CartModel>().add(...)context.read<CartModel>().add(...)context.select((CartModel c) => c.add(...))notifyListeners()?List.unmodifiable(_items), а не _items напрямую?notifyListeners, а значит, без перерисовки.