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 та доступні з будь-якого місця. Компілятор перевіряє типи, залежності та цілісність графа провайдерів на етапі збірки, виключаючи помилки часу виконання типу 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 — прапорець, що запобігає знищенню кешу провайдера при виході з області видимості.

Кодогенерація та @riverpod

Кодогенерація — ключова особливість Riverpod 2.0+. Анотація @riverpod над функцією автоматично генерує провайдер з правильним типом, підтримкою рефакторингу та автодоповненням. Кодогенерація використовує 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 через геттер/сеттер. 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 (вбудовано)
ТестуванняОбгортка в 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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