Riverpod — istota, kompilacja zależności we Flutter

Autor: IT Sectr Opublikowano: 2026-02-19 Czas czytania: 7 min

Riverpod — kompilowalny menedżer stanu i zależności dla Flutter, stworzony przez Rémi Roussela w 2021 roku jako następca Provider. Riverpod rozwiązuje fundamentalne problemy Provider: brak sprawdzania kompilacji, zależność od BuildContext i trudność z ProviderNotFoundException. Według danych pub.dev, pakiet zdobył ponad 5 tysięcy polubień i aktywnie wypiera Provider w nowych projektach.

Najważniejsze

  • ProviderRef — obiekt do dostępu do innych providerów wewnątrz providera
  • AsyncValue — opakowanie dla danych asynchronicznych ze stanami loading/error/data
  • Notifier — klasa dla mutowalnego stanu z metodami zmiany
  • ProviderScope — główny widget zarządzający wszystkimi providerami
  • Code Generation — adnotacje @riverpod do automatycznego generowania providerów

Czym jest Riverpod?

Riverpod — biblioteka do zarządzania stanem i wstrzykiwania zależności we Flutter, kompilująca opis providerów w bezpieczny kod Dart. W przeciwieństwie do Provider, providery Riverpod nie są powiązane z BuildContext: są tworzone globalnie lub w ProviderScope i dostępne z dowolnego miejsca. Kompilator sprawdza typy, zależności i integralność grafu providerów na etapie budowania, eliminując błędy runtime typu ProviderNotFoundException.

Riverpod używa modelu override do testowania: każdy provider może zostać nadpisany przez ProviderScope.overrideWith bez konieczności tworzenia podklas lub mockowania interfejsów. To sprawia, że testowanie jest izolowane: każdy test otrzymuje własną kopię grafu zależności, która jest w pełni kontrolowana.

Według danych Flutter Community Survey 2025, Riverpod zajmuje trzecie miejsce pod względem popularności po Provider i BLoC. Jednocześnie Riverpod jest najszybciej rozwijającym się pakietem: +120% instalacji w 2024 roku. Główne przyczyny: bezpieczeństwo kompilacji, brak ProviderNotFoundException, wbudowana obsługa asynchroniczności przez AsyncValue.

Typy providerów

Riverpod udostępnia 8 typów providerów, każdy dla konkretnego scenariusza: Provider (stała/usługa), StateProvider (prosty stan), StateNotifierProvider (złożona logika z StateNotifier), ChangeNotifierProvider (do migracji z Provider), FutureProvider (dane asynchroniczne, jednorazowo), StreamProvider (strumień reaktywny), NotifierProvider (nowe API, Flutter 3.10+) i AsyncNotifierProvider (asynchroniczny 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 — obiekt przekazywany każdemu providerowi do dostępu do innych providerów. ref.watch — subskrypcja zmian, ref.read — jednorazowe odczytanie, ref.invalidate — resetowanie pamięci podręcznej. ProviderRef zastępuje BuildContext z Provider: każdy provider może czytać inne providery bez dostępu do drzewa widgetów. Pozwala to budować graf zależności poza warstwą UI.

ProviderScope — główny widget, obowiązkowy do działania Riverpod. ProviderScope przechowuje wszystkie providery, zarządza ich cyklem życia i buforuje wartości. Bez ProviderScope aplikacja padnie z ProviderNotFoundException. ProviderScope może być zagnieżdżony — zagnieżdżony zakres nadpisuje providery nadrzędne, co jest używane do testowania i izolacji funkcji.

AsyncValue i praca z asynchronicznością

AsyncValue — sealed-klasa Riverpod do reprezentacji stanu asynchronicznego. AsyncValue ma trzy warianty: AsyncData (udane dane), AsyncError (błąd), AsyncLoading (ładowanie). Zamiast ręcznego przełączania między loading/error/data każdy provider FutureProvider lub StreamProvider automatycznie zwraca AsyncValue, a widget obsługuje wszystkie trzy stany przez 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 — metoda do dopasowywania wzorców wszystkich trzech stanów. Kompilator sprawdza, czy wszystkie trzy przypadki są obsłużone — jeśli zapomnimy o loading lub error, kod się nie skompiluje. AsyncValue.whenData — tylko dla data (gdy loading/error nie są potrzebne). AsyncValue.guard — opakowanie try-catch do konwersji wyjątków na AsyncError. keepAlive — flaga zapobiegająca niszczeniu pamięci podręcznej providera przy wyjściu z zakresu widoczności.

Code Generation i @riverpod

Kodgeneracja — kluczowa cecha Riverpod 2.0+. Adnotacja @riverpod nad funkcją automatycznie generuje provider z poprawnym typem, wsparciem refaktoryzacji i autouzupełnianiem. Kodgeneracja używa riverpod_generator i build_runner. Deweloper pisze czystą funkcję, a cała reszta — typy, klasy, konstruktory factory — jest generowana automatycznie.

Dart
@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello World';
}

// Wygenerowano: final helloWorldProvider = Provider((ref) => 'Hello World');

@riverpod
class Counter extends _$Counter {
  int build() => 0;
  void increment() => state++;
}

Notifier — nowe API dla mutowalnego stanu z kodgeneracją. Notifier to klasa z metodą build() i metodami zmiany stanu. W przeciwieństwie do StateNotifier, Notifier nie wymaga osobnej klasy stanu i daje bezpośredni dostęp do state przez getter/setter. Riverpod automatycznie generuje NotifierProvider dla każdej klasy Notifier z adnotacją @riverpod.

build_runner: kodgeneracja uruchamiana jest poleceniem dart run build_runner build. Wygenerowane pliki mają sufiks .g.dart i są importowane do kodu źródłowego. Przy zmianie adnotacji lub typów providerów należy ponownie uruchomić kodgenerację. Riverpod 2.x zaleca kodgenerację dla wszystkich nowych projektów — ręczne tworzenie providerów staje się przestarzałe.

Riverpod vs Provider

Główne różnice Riverpod od Provider: niezależność od BuildContext, bezpieczeństwo kompilacji, wbudowana praca z asynchronicznością, automatyczne buforowanie i testowanie przez override. Provider wymaga BuildContext do dostępu do stanu (context.watch, context.read), Riverpod używa WidgetRef i globalnie zadeklarowanych providerów.

CechaProviderRiverpod
Zależność od BuildContextTakNie
Sprawdzanie kompilacjiNieTak (przez @riverpod)
ProviderNotFoundExceptionRuntimeNiemożliwy
AsynchronicznośćRęcznaAsyncValue (wbudowany)
TestowanieOpakowanie w ProviderProviderScope.overrideWith
BuforowanieNieAutomatyczne + keepAlive

Migracja z Provider: Riverpod obsługuje ChangeNotifierProvider.adaptive do używania istniejących ChangeNotifier bez przepisywania. Migracja etapowa: najpierw nowe funkcje pisane są na Riverpod, następnie stare Provider są zastępowane providerami Riverpod przez adapter. Oba pakiety mogą współistnieć w jednym projekcie, co pozwala na migrację bez zamrażania rozwoju.

Testowanie Riverpod

Testowanie Riverpod opiera się na ProviderScope.overrideWith. Każdy provider jest nadpisywany wewnątrz testowego ProviderScope bez mocków i kontenerów DI. ProviderContainer — izolowane środowisko testowe bez Flutter (czysty Dart), umożliwiające testowanie providerów bez renderowania widgetów.

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 — bez Flutter. Użyj ProviderContainer do testów jednostkowych providerów bez widgetów. overrideWithValue — zastąpienie providera konkretną wartością. overrideWith — zastąpienie fabryką providera (do mockowania usług). autodispose — w testach sprawdzaj, czy provider jest niszczony przy wyjściu z zakresu widoczności, za pomocą container.dispose().

Często zadawane pytania

Czym Riverpod różni się od BLoC?

Riverpod — biblioteka zarządzania stanem z globalnymi providerami, AsyncValue i kodgeneracją. BLoC — wzorzec architektoniczny z Event → Stream → State. Riverpod jest łatwiejszy w nauce i ma lepsze DX dzięki adnotacjom @riverpod. BLoC zapewnia ścisłą izolację logiki biznesowej i śledzenie Event przez BlocObserver. Wybór zależy od paradygmatu projektu: Riverpod jest bliższy Provider, BLoC — strumieniom reaktywnym.

Czym jest autodispose w Riverpod?

Autodispose — mechanizm automatycznego niszczenia providera, gdy nikt nie jest na niego subskrybowany. Domyślnie wszystkie providery Riverpod są autodispose: przy wyjściu widgeta z drzewa provider jest usuwany z pamięci. keepAlive — flaga wyłączająca autodispose dla providerów, które powinny żyć zawsze (klienci API, repozytoria, ustawienia). Zapobiega to wyciekom pamięci — nieużywane providery są automatycznie niszczone.

Jak działa ref.invalidate?

ref.invalidate — metoda wymuszająca resetowanie pamięci podręcznej providera. Po invalidate provider jest odtwarzany przy następnym odczycie: FutureProvider ponownie wykonuje funkcję async, StreamProvider ponownie subskrybuje strumień. Użyj invalidate do wymuszonego odświeżenia danych (pull-to-refresh, zmiana użytkownika). ref.refresh — kombinacja invalidate + odczyt: resetuje i natychmiast odczytuje nową wartość w jednej operacji.

Czy można używać Riverpod bez kodgeneracji?

Tak. Riverpod 1.x działa tylko bez kodgeneracji — providery są tworzone ręcznie przez Provider(), StateNotifierProvider(), FutureProvider() itd. Riverpod 2.x obsługuje oba podejścia. Bez kodgeneracji jest więcej boilerplate, ale brak zależności od build_runner i dart run build_runner build. Dla małych projektów (do 30 providerów) ręczne tworzenie jest uzasadnione, dla dużych — kodgeneracja jest obowiązkowa.

Czym są Family-provider?

Family — modyfikator providera przyjmujący zewnętrzny parametr. Na przykład userProvider(123) — provider ładujący użytkownika o ID 123. Family-provider buforują wynik dla każdego unikalnego parametru osobno. Użyj Family dla listy elementów, gdzie każdy element jest ładowany po ID. Modyfikator Family jest dostępny dla wszystkich typów providerów: Provider.family, FutureProvider.family, StreamProvider.family.

Podsumowanie

  • Riverpod — kompilowalny menedżer stanu, następca Provider bez ProviderNotFoundException
  • ProviderRef — zamiennik BuildContext do dostępu do providerów wewnątrz innych providerów
  • AsyncValue — sealed-klasa ze stanami loading/error/data dla danych asynchronicznych
  • Kodgeneracja @riverpod — automatyczne wnioskowanie typów i fabryk providerów
  • ProviderScope.overrideWith — izolowane testowanie bez mocków i kontenerów DI
  • Family — sparametryzowane providery z indywidualnym buforowaniem
  • autodispose i keepAlive — automatyczne zarządzanie cyklem życia providerów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również