Современное управление состоянием: Provider и Riverpod
Когда данные нужны многим экранам, прокидывать их через дюжину колбэков невыносимо — на помощь приходят стейт-менеджеры.
Суть: Provider, Riverpod и Bloc выносят состояние из дерева виджетов в отдельный слой. Виджеты «подписываются» на нужные данные и перестраиваются автоматически, без ручного проброса через параметры.
Чтобы выбор стейт-менеджера не казался гаданием, держите в голове простую лестницу сложности. Локальное состояние одного виджета — setState. Состояние, общее для пары соседних виджетов, — поднятие состояния. Состояние, нужное многим экранам на разной глубине, — стейт-менеджер. Эта лестница избавляет от двух крайностей: тащить тяжёлую библиотеку в простой счётчик и героически прокидывать данные через двадцать виджетов вручную.
Поднятие состояния отлично работает, пока дерево неглубокое. Но в реальном приложении данные о пользователе нужны и в шапке, и в профиле, и в настройках — на разной глубине. Прокидывать их через каждый промежуточный виджет (prop drilling) — мучение. Стейт-менеджеры решают это: данные живут отдельно, а любой виджет берёт их напрямую.
В 2025 году ландшафт устоялся. Provider — простой и официально поддерживаемый, хорош для старта. Riverpod (по сути, преемник Provider) — типобезопасный, тестируемый, не зависит от BuildContext; считается лучшим выбором для масштабируемых приложений. Bloc — строгая архитектура на событиях и состояниях, для крупных команд. Появились и нативные Signals для локальной реактивности.
// Riverpod: провайдер состояния (упрощённо)
final counterProvider = StateProvider<int>((ref) => 0);
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // подписка на данные
return Column(children: [
Text('Счёт: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('+'),
),
]);
}
}
Как работают подписки под капотом
Стейт-менеджер хранит данные в провайдере вне дерева виджетов. Виджет подписывается (например, ref.watch в Riverpod). Когда данные меняются, провайдер уведомляет всех подписчиков, и только они перестраиваются — остальное дерево не трогается. Это и эффективнее, и чище, чем прокидывать колбэки через десять уровней.
+-----------------------+
| counterProvider | <- состояние вне дерева
| count = 5 |
+-----------------------+
/ | \
watch watch read
| | |
Шапка Профиль Кнопка(+)
(count) (count) меняет состояние
|
изменилось -> уведомление -> ТОЛЬКО подписчики перестроены
# Модель провайдера с подписками (паттерн наблюдатель)
class Provider:
def __init__(self, value):
self.value = value
self.subscribers = []
def watch(self, name):
self.subscribers.append(name) # виджет подписался
return self.value
def update(self, new_value):
self.value = new_value # данные изменились
for s in self.subscribers: # уведомляем подписчиков
print(f'{s} перестроен -> {self.value}')
counter = Provider(0)
counter.watch('Шапка')
counter.watch('Профиль')
counter.update(1) # оба подписчика перестроились
Частые ошибки
- Тащить стейт-менеджер в крошечное приложение — для простого экрана достаточно
setState. - Подписываться (
watch) там, где нужно лишь прочитать единожды — лишние перестроения. Для разовых действий используйтеread. - Смешивать несколько стейт-менеджеров без причины — выберите один и держитесь его.
Best practices
- Для локального состояния одного виджета —
setState; для общего — стейт-менеджер. - Для новых проектов в 2025 году Riverpod — крепкий выбор: типобезопасен, тестируем, независим от контекста.
- Разделяйте подписку на данные (
watch) и разовое чтение/действие (read).
Под капотом почти все стейт-менеджеры опираются на встроенный механизм Flutter — InheritedWidget, который умеет эффективно раздавать данные вниз по дереву и уведомлять только заинтересованных подписчиков. Provider и Riverpod — это удобные обёртки над этой идеей, скрывающие её рутину. Знать про InheritedWidget полезно: он объясняет, как вообще данные «телепортируются» сквозь дерево без проброса через каждый виджет.
Итог: стейт-менеджеры выносят данные из дерева и устраняют prop drilling. setState остаётся для локального состояния, а Provider/Riverpod/Bloc — для общего. Дальше посмотрим, как состояние сочетается с асинхронными данными.