Современное управление состоянием: 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 — для общего. Дальше посмотрим, как состояние сочетается с асинхронными данными.

Проверьте себя
1. Какую проблему решают стейт-менеджеры вроде Riverpod и Provider?
AУскоряют компиляцию
BИзбавляют от прокидывания данных через множество виджетов (prop drilling)
CЗаменяют язык Dart
DРисуют анимации
2. Что считается крепким выбором стейт-менеджера для новых проектов в 2025 году?
AТолько setState всегда
BRiverpod — типобезопасный, тестируемый, не зависит от BuildContext
CInheritedWidget вручную
DГлобальные переменные