Riverpod — компилируемый менеджер состояния и зависимостей для Flutter, созданный Реми Русле в 2021 году как наследник Provider. Riverpod решает фундаментальные проблемы Provider: отсутствие компиляционной проверки, зависимость от BuildContext и сложность с ProviderNotFoundException. По данным pub.dev, пакет набрал более 5 тысяч лайков и активно вытесняет Provider в новых проектах.
Главное
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).
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 — sealed-класс Riverpod для представления асинхронного состояния. AsyncValue имеет три варианта: AsyncData (успешные данные), AsyncError (ошибка), AsyncLoading (загрузка). Вместо ручного переключения между loading/error/data каждый провайдер FutureProvider или StreamProvider автоматически возвращает AsyncValue, и виджет обрабатывает все три состояния через ref.watch.
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 — флаг, предотвращающий уничтожение кеша провайдера при выходе из области видимости.
Кодогенерация — ключевая особенность Riverpod 2.0+. Аннотация @riverpod над функцией автоматически генерирует провайдер с правильным типом, поддержкой рефакторинга и автодополнением. Кодогенерация использует riverpod_generator и build_runner. Разработчик пишет чистую функцию, а всё остальное — типы, классы, factory-конструкторы — генерируется автоматически.
@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 от Provider: независимость от BuildContext, компиляционная безопасность, встроенная работа с асинхронностью, автокеширование и тестирование через override. Provider требует BuildContext для доступа к состоянию (context.watch, context.read), Riverpod использует WidgetRef и глобально объявленные провайдеры.
| Характеристика | Provider | Riverpod |
|---|---|---|
| Зависимость от BuildContext | Да | Нет |
| Компиляционная проверка | Нет | Да (через @riverpod) |
| ProviderNotFoundException | Runtime | Невозможен |
| Асинхронность | Ручная | AsyncValue (built-in) |
| Тестирование | Обёртка в Provider | ProviderScope.overrideWith |
| Кеширование | Нет | Автоматическое + keepAlive |
Миграция с Provider: Riverpod поддерживает ChangeNotifierProvider.adaptive для использования существующих ChangeNotifier без переписывания. Поэтапная миграция: сначала новые фичи пишутся на Riverpod, затем старые Provider заменяются на Riverpod-провайдеры через адаптер. Оба пакета могут сосуществовать в одном проекте, что позволяет мигрировать без заморозки разработки.
Тестирование Riverpod строится на ProviderScope.overrideWith. Каждый провайдер переопределяется внутри тестового ProviderScope без моков и DI-контейнеров. ProviderContainer — изолированная среда для тестов без Flutter (чистый 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 — библиотека управления состоянием с глобальными провайдерами, AsyncValue и кодогенерацией. BLoC — архитектурный паттерн с Event → Stream → State. Riverpod проще в изучении и имеет лучшую DX через @riverpod-аннотации. BLoC даёт строгую изоляцию бизнес-логики и трассировку Event через BlocObserver. Выбор зависит от парадигмы проекта: Riverpod ближе к Provider, BLoC — к реактивным потокам.
Autodispose — механизм автоматического уничтожения провайдера, когда на него никто не подписан. По умолчанию все Riverpod-провайдеры autodispose: при выходе виджета из дерева провайдер удаляется из памяти. keepAlive — флаг, отключающий autodispose для провайдеров, которые должны жить всегда (API-клиенты, репозитории, настройки). Это предотвращает утечки памяти — неиспользуемые провайдеры уничтожаются автоматически.
ref.invalidate — метод, принудительно сбрасывающий кеш провайдера. После invalidate провайдер пересоздаётся при следующем чтении: FutureProvider повторно выполняет async-функцию, StreamProvider переподписывается на поток. Используйте invalidate для принудительного обновления данных (pull-to-refresh, смена пользователя). ref.refresh — комбинация invalidate + чтение: сбрасывает и сразу читает новое значение в одной операции.
Да. Riverpod 1.x работает только без кодогенерации — провайдеры создаются вручную через Provider(), StateNotifierProvider(), FutureProvider() и т.д. Riverpod 2.x поддерживает оба подхода. Без кодогенерации больше boilerplate, но нет зависимости от build_runner и dart run build_runner build. Для небольших проектов (до 30 провайдеров) ручное создание оправдано, для крупных — кодогенерация обязательна.
Family — модификатор провайдера, принимающий внешний параметр. Например, userProvider(123) — провайдер, загружающий пользователя с ID 123. Family-провайдеры кешируют результат для каждого уникального параметра отдельно. Используйте Family для списка элементов, где каждый элемент загружается по ID. Family-модификатор доступен для всех типов провайдеров: Provider.family, FutureProvider.family, StreamProvider.family.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также