Riverpod — компилируем мениджър на състояние и зависимости за Flutter, създаден от Rémi Roussel през 2021 г. като наследник на Provider. Riverpod решава фундаменталните проблеми на Provider: липса на компилационна проверка, зависимост от BuildContext и трудност с ProviderNotFoundException. По данни на pub.dev, пакетът е събрал над 5 хиляди харесвания и активно измества Provider в нови проекти.
Основни неща
Riverpod — библиотека за управление на състояние и инжектиране на зависимости във Flutter, която компилира описанието на provider-ите в безопасен Dart код. За разлика от Provider, provider-ите на Riverpod не са обвързани с BuildContext: те се създават глобално или в ProviderScope и са достъпни от всяко място. Компилаторът проверява типовете, зависимостите и целостта на графа от provider-и по време на компилация, елиминирайки runtime грешки като ProviderNotFoundException.
Riverpod използва модела override за тестване: всеки provider може да бъде заменен чрез ProviderScope.overrideWith без необходимост от създаване на подкласове или mock-ване на интерфейси. Това прави тестването изолирано: всеки тест получава свое копие на графа от зависимости, което е напълно контролирано.
Според Flutter Community Survey 2025, Riverpod заема трето място по популярност след Provider и BLoC. В същото време Riverpod е най-бързо растящият пакет: +120% инсталации през 2024 г. Основни причини: компилационна безопасност, липса на ProviderNotFoundException, вградена поддръжка на асинхронност чрез AsyncValue.
Riverpod предоставя 8 типа provider-и, всеки за конкретен сценарий: 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 — обектът, предаван на всеки provider за достъп до други provider-и. ref.watch — абонамент за промени, ref.read — еднократно четене, ref.invalidate — нулиране на кеша. ProviderRef замества BuildContext от Provider: всеки provider може да чете други provider-и без достъп до дървото на widget-ите. Това позволява изграждане на граф от зависимости извън UI слоя.
ProviderScope — задължителният коренов widget за работа на Riverpod. ProviderScope съхранява всички provider-и, управлява жизнения им цикъл и кешира стойности. Без ProviderScope приложението ще се срине с ProviderNotFoundException. ProviderScope може да бъде вложен — вложеният scope замества provider-ите на родителя, което се използва за тестване и изолиране на функционалности.
AsyncValue — sealed клас на Riverpod за представяне на асинхронно състояние. AsyncValue има три варианта: AsyncData (успешни данни), AsyncError (грешка), AsyncLoading (зареждане). Вместо ръчно превключване между loading/error/data, всеки FutureProvider или StreamProvider автоматично връща AsyncValue, а widget-ът обработва и трите състояния чрез 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 — флаг, предотвратяващ унищожаването на кеша на provider при излизане от обхвата на видимост.
Кодогенерация — ключова характеристика на Riverpod 2.0+. Анотацията @riverpod над функция автоматично генерира provider с правилния тип, поддръжка на рефакториране и автоматично довършване. Кодогенерацията използва 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 чрез getter/setter. Riverpod автоматично генерира NotifierProvider за всеки Notifier клас с анотация @riverpod.
build_runner: кодогенерацията се стартира с команда dart run build_runner build. Генерираните файлове имат суфикс .g.dart и се импортират в изходния код. При промяна на анотациите или типовете provider-и трябва да се рестартира кодогенерацията. Riverpod 2.x препоръчва кодогенерация за всички нови проекти — ръчното създаване на provider-и остарява.
Основни разлики между Riverpod и Provider: независимост от BuildContext, компилационна безопасност, вградена работа с асинхронност, автоматично кеширане и тестване чрез override. Provider изисква BuildContext за достъп до състояние (context.watch, context.read), Riverpod използва WidgetRef и глобално декларирани provider-и.
| Характеристика | Provider | Riverpod |
|---|---|---|
| Зависимост от BuildContext | Да | Не |
| Компилационна проверка | Не | Да (чрез @riverpod) |
| ProviderNotFoundException | Runtime | Невъзможен |
| Асинхронност | Ръчна | AsyncValue (вграден) |
| Тестване | Обвивка в Provider | ProviderScope.overrideWith |
| Кеширане | Не | Автоматично + keepAlive |
Миграция от Provider: Riverpod поддържа ChangeNotifierProvider.adaptive за използване на съществуващи ChangeNotifier без презаписване. Поетапна миграция: първо новите функционалности се пишат на Riverpod, след това старите Provider се заменят с Riverpod provider-и чрез адаптер. И двата пакета могат да съществуват съвместно в един проект, което позволява миграция без замразяване на разработката.
Тестването на Riverpod се основава на ProviderScope.overrideWith. Всеки provider се заменя вътре в тестовия ProviderScope без mock-ове и DI контейнери. ProviderContainer — изолирана среда за тестове без Flutter (чист Dart), позволяваща тестване на provider-и без визуализиране на widget-и.
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 за unit тестове на provider-и без widget-и. overrideWithValue — замяна на provider с конкретна стойност. overrideWith — замяна с фабрика за provider (за mock-ване на услуги). autodispose — в тестовете проверете дали provider се унищожава при излизане от обхвата на видимост с container.dispose().
Често задавани въпроси
Riverpod — библиотека за управление на състояние с глобални provider-и, AsyncValue и кодогенерация. BLoC — архитектурен модел с Event → Stream → State. Riverpod е по-лесен за изучаване и има по-добро изживяване за разработчици чрез @riverpod анотациите. BLoC осигурява строга изолация на бизнес логиката и проследяване на Event чрез BlocObserver. Изборът зависи от парадигмата на проекта: Riverpod е по-близо до Provider, BLoC — до реактивните потоци.
Autodispose — механизъм за автоматично унищожаване на provider, когато никой не е абониран за него. По подразбиране всички Riverpod provider-и са autodispose: при излизане на widget от дървото, provider се премахва от паметта. keepAlive — флаг, изключващ autodispose за provider-и, които трябва да живеят винаги (API клиенти, хранилища, настройки). Това предотвратява изтичане на памет — неизползваните provider-и се унищожават автоматично.
ref.invalidate — метод, принудително нулиращ кеша на provider. След invalidate, provider се пресъздава при следващото четене: FutureProvider изпълнява отново async функцията, StreamProvider се абонира отново за потока. Използвайте invalidate за принудително обновяване на данни (pull-to-refresh, смяна на потребител). ref.refresh — комбинация от invalidate + четене: нулира и веднага прочита новата стойност в една операция.
Да. Riverpod 1.x работи само без кодогенерация — provider-ите се създават ръчно чрез Provider(), StateNotifierProvider(), FutureProvider() и т.н. Riverpod 2.x поддържа и двата подхода. Без кодогенерация има повече boilerplate, но няма зависимост от build_runner и dart run build_runner build. За малки проекти (до 30 provider-и) ръчното създаване е оправдано, за големи проекти кодогенерацията е задължителна.
Family — модификатор на provider, който приема външен параметър. Например userProvider(123) — provider, зареждащ потребител с ID 123. Family provider-ите кешират резултата за всеки уникален параметър отделно. Използвайте Family за списък от елементи, където всеки елемент се зарежда по ID. Модификаторът Family е достъпен за всички типове provider-и: Provider.family, FutureProvider.family, StreamProvider.family.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също