Riverpod — същност, компилация на зависимости във Flutter

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

Riverpod — компилируем мениджър на състояние и зависимости за Flutter, създаден от Rémi Roussel през 2021 г. като наследник на Provider. Riverpod решава фундаменталните проблеми на Provider: липса на компилационна проверка, зависимост от BuildContext и трудност с ProviderNotFoundException. По данни на pub.dev, пакетът е събрал над 5 хиляди харесвания и активно измества Provider в нови проекти.

Основни неща

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

Какво е Riverpod?

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.

Типове provider-и

Riverpod предоставя 8 типа provider-и, всеки за конкретен сценарий: 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 — обектът, предаван на всеки 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 и работа с асинхронност

AsyncValue — sealed клас на Riverpod за представяне на асинхронно състояние. AsyncValue има три варианта: AsyncData (успешни данни), AsyncError (грешка), AsyncLoading (зареждане). Вместо ръчно превключване между loading/error/data, всеки FutureProvider или StreamProvider автоматично връща AsyncValue, а widget-ът обработва и трите състояния чрез 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 — флаг, предотвратяващ унищожаването на кеша на provider при излизане от обхвата на видимост.

Code Generation и @riverpod

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

Dart
@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

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

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

Миграция от Provider: Riverpod поддържа ChangeNotifierProvider.adaptive за използване на съществуващи ChangeNotifier без презаписване. Поетапна миграция: първо новите функционалности се пишат на Riverpod, след това старите Provider се заменят с Riverpod provider-и чрез адаптер. И двата пакета могат да съществуват съвместно в един проект, което позволява миграция без замразяване на разработката.

Тестване на Riverpod

Тестването на Riverpod се основава на ProviderScope.overrideWith. Всеки provider се заменя вътре в тестовия ProviderScope без mock-ове и DI контейнери. ProviderContainer — изолирана среда за тестове без Flutter (чист Dart), позволяваща тестване на provider-и без визуализиране на widget-и.

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 за unit тестове на provider-и без widget-и. overrideWithValue — замяна на provider с конкретна стойност. overrideWith — замяна с фабрика за provider (за mock-ване на услуги). autodispose — в тестовете проверете дали provider се унищожава при излизане от обхвата на видимост с container.dispose().

Често задавани въпроси

Как се различава Riverpod от BLoC?

Riverpod — библиотека за управление на състояние с глобални provider-и, AsyncValue и кодогенерация. BLoC — архитектурен модел с Event → Stream → State. Riverpod е по-лесен за изучаване и има по-добро изживяване за разработчици чрез @riverpod анотациите. BLoC осигурява строга изолация на бизнес логиката и проследяване на Event чрез BlocObserver. Изборът зависи от парадигмата на проекта: Riverpod е по-близо до Provider, BLoC — до реактивните потоци.

Какво е autodispose в Riverpod?

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

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

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

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

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

Какво представляват Family provider-ите?

Family — модификатор на provider, който приема външен параметър. Например userProvider(123) — provider, зареждащ потребител с ID 123. Family provider-ите кешират резултата за всеки уникален параметър отделно. Използвайте Family за списък от елементи, където всеки елемент се зарежда по ID. Модификаторът Family е достъпен за всички типове provider-и: Provider.family, FutureProvider.family, StreamProvider.family.

Резюме

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

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също