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
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.
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).
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 — 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.
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.
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.
@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.
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.
| Cecha | Provider | Riverpod |
|---|---|---|
| Zależność od BuildContext | Tak | Nie |
| Sprawdzanie kompilacji | Nie | Tak (przez @riverpod) |
| ProviderNotFoundException | Runtime | Niemożliwy |
| Asynchroniczność | Ręczna | AsyncValue (wbudowany) |
| Testowanie | Opakowanie w Provider | ProviderScope.overrideWith |
| Buforowanie | Nie | Automatyczne + 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 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.
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
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.
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.
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.
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.
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
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.
Przeczytaj również