Riverpod — суть, компиляция зависимостей во Flutter

Автор: IT Sectr Опубликовано: 2026-02-19 Время чтения: 7 мин

Riverpod — компилируемый менеджер состояния и зависимостей для Flutter, созданный Реми Русле в 2021 году как наследник Provider. Riverpod решает фундаментальные проблемы Provider: отсутствие компиляционной проверки, зависимость от BuildContext и сложность с ProviderNotFoundException. По данным pub.dev, пакет набрал более 5 тысяч лайков и активно вытесняет Provider в новых проектах.

Главное

  • ProviderRef — объект для доступа к другим провайдерам внутри провайдера
  • AsyncValue — обёртка для асинхронных данных с состоянием loading/error/data
  • Notifier — класс для мутабельного состояния с методами изменения
  • ProviderScope — корневой виджет, управляющий всеми провайдерами
  • Code Generation — аннотации @riverpod для автоматической генерации провайдеров

Что такое Riverpod?

Riverpod — библиотека для управления состоянием и внедрения зависимостей во Flutter, компилирующая описание провайдеров в безопасный Dart-код. В отличие от Provider, провайдеры Riverpod не привязаны к BuildContext: они создаются глобально или в ProviderScope и доступны из любого места. Компилятор проверяет типы, зависимости и целостность графа провайдеров на этапе сборки, исключая runtime-ошибки типа ProviderNotFoundException.

Riverpod использует модель override для тестирования: каждый провайдер может быть переопределён через ProviderScope.overrideWith без необходимости создавать подклассы или мокать интерфейсы. Это делает тестирование изолированным: каждый тест получает свою копию графа зависимостей, которая полностью контролируется.

По данным Flutter Community Survey 2025, Riverpod занимает третье место по популярности после Provider и BLoC. При этом Riverpod — самый быстрорастущий пакет: +120% установок за 2024 год. Основные причины: компиляционная безопасность, отсутствие ProviderNotFoundException, встроенная поддержка асинхронности через AsyncValue.

Типы провайдеров

Riverpod предоставляет 8 типов провайдеров, каждый для конкретного сценария: Provider (константа/сервис), StateProvider (примитивное состояние), StateNotifierProvider (сложная логика с StateNotifier), ChangeNotifierProvider (для миграции с Provider), FutureProvider (асинхронные данные, один раз), StreamProvider (реактивный поток), NotifierProvider (новый API, Flutter 3.10+) и AsyncNotifierProvider (асинхронный Notifier).

Dart
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

ProviderRef — объект, передаваемый каждому провайдеру для доступа к другим провайдерам. ref.watch — подписка на изменения, ref.read — однократное чтение, ref.invalidate — сброс кеша. ProviderRef заменяет BuildContext из Provider: любой провайдер может читать другие провайдеры без доступа к дереву виджетов. Это позволяет строить граф зависимостей вне UI-слоя.

ProviderScope — корневой виджет, обязательный для работы Riverpod. ProviderScope хранит все провайдеры, управляет их жизненным циклом и кеширует значения. Без ProviderScope приложение упадёт с ProviderNotFoundException. ProviderScope может быть вложенным — вложенный scope переопределяет провайдеры родительского, что используется для тестирования и изоляции фич.

AsyncValue и работа с асинхронностью

AsyncValue — sealed-класс Riverpod для представления асинхронного состояния. AsyncValue имеет три варианта: AsyncData (успешные данные), AsyncError (ошибка), AsyncLoading (загрузка). Вместо ручного переключения между loading/error/data каждый провайдер FutureProvider или StreamProvider автоматически возвращает AsyncValue, и виджет обрабатывает все три состояния через ref.watch.

Dart
final userProvider = FutureProvider((ref) async {
  final api = ref.watch(apiProvider);
  return await api.fetchUser();
});

class UserScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    return userAsync.when(
      data: (user) => UserWidget(user),
      error: (e, _) => ErrorWidget(e.toString()),
      loading: () => CircularProgressIndicator(),
    );
  }
}

AsyncValue.when — метод для паттерн-матчинга всех трёх состояний. Компилятор проверяет, что все три случая обработаны — если забыть loading или error, код не скомпилируется. AsyncValue.whenData — только для data (если loading/error не нужны). AsyncValue.guard — обёртка над try-catch для конвертации исключения в AsyncError. keepAlive — флаг, предотвращающий уничтожение кеша провайдера при выходе из области видимости.

Code Generation и @riverpod

Кодогенерация — ключевая особенность Riverpod 2.0+. Аннотация @riverpod над функцией автоматически генерирует провайдер с правильным типом, поддержкой рефакторинга и автодополнением. Кодогенерация использует riverpod_generator и build_runner. Разработчик пишет чистую функцию, а всё остальное — типы, классы, factory-конструкторы — генерируется автоматически.

Dart
@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello World';
}

// Generated: final helloWorldProvider = Provider((ref) => 'Hello World');

@riverpod
class Counter extends _$Counter {
  int build() => 0;
  void increment() => state++;
}

Notifier — новый API для мутабельного состояния с кодогенерацией. Notifier — класс с методом build() и методами изменения состояния. В отличие от StateNotifier, Notifier не требует отдельного класса состояния и даёт прямой доступ к state через геттер/сеттер. Riverpod автоматически генерирует NotifierProvider для каждого Notifier-класса с аннотацией @riverpod.

build_runner: кодогенерация запускается командой dart run build_runner build. Сгенерированные файлы имеют суффикс .g.dart и импортируются в исходный код. При изменении аннотаций или типов провайдеров нужно перезапустить кодогенерацию. Riverpod 2.x рекомендует кодогенерацию для всех новых проектов — ручное создание провайдеров устаревает.

Riverpod vs Provider

Главные отличия Riverpod от Provider: независимость от BuildContext, компиляционная безопасность, встроенная работа с асинхронностью, автокеширование и тестирование через override. Provider требует BuildContext для доступа к состоянию (context.watch, context.read), Riverpod использует WidgetRef и глобально объявленные провайдеры.

ХарактеристикаProviderRiverpod
Зависимость от BuildContextДаНет
Компиляционная проверкаНетДа (через @riverpod)
ProviderNotFoundExceptionRuntimeНевозможен
АсинхронностьРучнаяAsyncValue (built-in)
ТестированиеОбёртка в ProviderProviderScope.overrideWith
КешированиеНетАвтоматическое + keepAlive

Миграция с Provider: Riverpod поддерживает ChangeNotifierProvider.adaptive для использования существующих ChangeNotifier без переписывания. Поэтапная миграция: сначала новые фичи пишутся на Riverpod, затем старые Provider заменяются на Riverpod-провайдеры через адаптер. Оба пакета могут сосуществовать в одном проекте, что позволяет мигрировать без заморозки разработки.

Тестирование Riverpod

Тестирование Riverpod строится на ProviderScope.overrideWith. Каждый провайдер переопределяется внутри тестового ProviderScope без моков и DI-контейнеров. ProviderContainer — изолированная среда для тестов без Flutter (чистый Dart), позволяющая тестировать провайдеры без отрисовки виджетов.

Dart
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  test('Counter increments correctly', () {
    final container = ProviderContainer();
    container.read(counterProvider.notifier).increment();
    expect(container.read(counterProvider), 1);
  });

  testWidgets('UI updates on increment', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [counterProvider.overrideWithValue(5)],
        child: CounterScreen(),
      ),
    );
    expect(find.text('5'), findsOneWidget);
  });
}

ProviderContainer — без Flutter. Используйте ProviderContainer для юнит-тестов провайдеров без виджетов. overrideWithValue — замена провайдера конкретным значением. overrideWith — замена фабрикой провайдера (для мокирования сервисов). autodispose — в тестах проверяйте, что провайдер уничтожается при выходе из области видимости, с помощью container.dispose().

Часто задаваемые вопросы

Чем Riverpod отличается от BLoC?

Riverpod — библиотека управления состоянием с глобальными провайдерами, AsyncValue и кодогенерацией. BLoC — архитектурный паттерн с Event → Stream → State. Riverpod проще в изучении и имеет лучшую DX через @riverpod-аннотации. BLoC даёт строгую изоляцию бизнес-логики и трассировку Event через BlocObserver. Выбор зависит от парадигмы проекта: Riverpod ближе к Provider, BLoC — к реактивным потокам.

Что такое autodispose в Riverpod?

Autodispose — механизм автоматического уничтожения провайдера, когда на него никто не подписан. По умолчанию все Riverpod-провайдеры autodispose: при выходе виджета из дерева провайдер удаляется из памяти. keepAlive — флаг, отключающий autodispose для провайдеров, которые должны жить всегда (API-клиенты, репозитории, настройки). Это предотвращает утечки памяти — неиспользуемые провайдеры уничтожаются автоматически.

Как работает ref.invalidate?

ref.invalidate — метод, принудительно сбрасывающий кеш провайдера. После invalidate провайдер пересоздаётся при следующем чтении: FutureProvider повторно выполняет async-функцию, StreamProvider переподписывается на поток. Используйте invalidate для принудительного обновления данных (pull-to-refresh, смена пользователя). ref.refresh — комбинация invalidate + чтение: сбрасывает и сразу читает новое значение в одной операции.

Можно ли использовать Riverpod без кодогенерации?

Да. Riverpod 1.x работает только без кодогенерации — провайдеры создаются вручную через Provider(), StateNotifierProvider(), FutureProvider() и т.д. Riverpod 2.x поддерживает оба подхода. Без кодогенерации больше boilerplate, но нет зависимости от build_runner и dart run build_runner build. Для небольших проектов (до 30 провайдеров) ручное создание оправдано, для крупных — кодогенерация обязательна.

Что такое Family-провайдеры?

Family — модификатор провайдера, принимающий внешний параметр. Например, userProvider(123) — провайдер, загружающий пользователя с ID 123. Family-провайдеры кешируют результат для каждого уникального параметра отдельно. Используйте Family для списка элементов, где каждый элемент загружается по ID. Family-модификатор доступен для всех типов провайдеров: Provider.family, FutureProvider.family, StreamProvider.family.

Итоги

  • Riverpod — компилируемый менеджер состояния, наследник Provider без ProviderNotFoundException
  • ProviderRef — замена BuildContext для доступа к провайдерам внутри других провайдеров
  • AsyncValue — sealed-класс с состояниями loading/error/data для асинхронных данных
  • Кодогенерация @riverpod — автоматический вывод типов и фабрик провайдеров
  • ProviderScope.overrideWith — изолированное тестирование без моков и DI-контейнеров
  • Family — параметризованные провайдеры с индивидуальным кешированием
  • autodispose и keepAlive — автоматическое управление жизненным циклом провайдеров

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также