Riverpod — компільований менеджер стану та залежностей для Flutter, створений Ремі Русле у 2021 році як наступник Provider. Riverpod вирішує фундаментальні проблеми Provider: відсутність компіляційної перевірки, залежність від BuildContext та складність із ProviderNotFoundException. За даними pub.dev, пакет набрав понад 5 тисяч лайків і активно витісняє Provider у нових проєктах.
Головне
Riverpod — бібліотека для керування станом та впровадження залежностей для Flutter, яка компілює опис провайдерів у безпечний Dart-код. На відміну від Provider, провайдери Riverpod не прив'язані до BuildContext: вони створюються глобально або в ProviderScope та доступні з будь-якого місця. Компілятор перевіряє типи, залежності та цілісність графа провайдерів на етапі збірки, виключаючи помилки часу виконання типу 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';
}
// Згенеровано: 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 (вбудовано) |
| Тестування | Обгортка в 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.