Od Provider do Riverpod: migracja starszego zarządzania stanem
Konwertuj istniejący kod ChangeNotifier i Provider na bezpieczny podczas kompilacji graf providerów Riverpod
Od Provider do Riverpod: migracja starszego zarządzania stanem to bezpłatna lekcja Flutter Mobile Development na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Flutter Mobile Development, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
Why Migrate to Riverpod?
The classic provider package solved dependency injection in Flutter, but it has real weaknesses you have probably hit in production:
- Runtime crashes — calling
context.read<T>()for a type that was never provided throws aProviderNotFoundExceptionat runtime, not compile time. - BuildContext coupling — you can only read providers where you have a
BuildContext. - No combining — depending on one
ChangeNotifierfrom another is awkward and error-prone.
Riverpod is a rewrite by the same author. Providers are top-level globals that the compiler can verify, so a missing dependency becomes a compile error. This lesson walks you through migrating a legacy ChangeNotifier + Provider app to Riverpod 2.0, piece by piece.
The Legacy Code We Are Migrating
Here is a typical legacy counter feature using ChangeNotifier. It exposes a value and a method that calls notifyListeners(). This is the pattern we will convert step by step.
Notice that the state (_count) and the mutation logic live together inside a class that extends ChangeNotifier.
// LEGACY — provider package
import 'package:flutter/foundation.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}How the Legacy Model Was Wired Up
In the old setup you register the model with ChangeNotifierProvider high in the widget tree, then read it via context.watch / context.read. The problem: if you forget to register it, the app compiles fine and crashes only when that screen opens.
// LEGACY wiring
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
),
);
}
// In a widget:
final count = context.watch<CounterModel>().count;
context.read<CounterModel>().increment();Step 1 — Install Riverpod and Add ProviderScope
Add flutter_riverpod to pubspec.yaml. The single most important wiring change is to wrap your app in a ProviderScope. This is the container that stores the state of every provider — it replaces the nest of ChangeNotifierProvider widgets at the root.
- Old: many provider widgets wrapping
MyApp. - New: one
ProviderScope. Providers themselves are declared as globals, not in the tree.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}Step 2 — ChangeNotifier becomes Notifier
Riverpod 2.0 introduces Notifier as the modern, type-safe replacement for ChangeNotifier. The differences:
- You hold state in a single
statefield instead of private fields + getters. - You never call
notifyListeners()— reassigningstaterebuilds listeners automatically. - The
build()method returns the initial state.
Here is the migrated counter as a Notifier<int>:
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterNotifier extends Notifier<int> {
@override
int build() => 0; // initial state
void increment() => state = state + 1;
void reset() => state = 0;
}
final counterProvider = NotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);Step 3 — Read State in the UI with ConsumerWidget
Replace context.watch/context.read with a WidgetRef. The cleanest path is to make your widget a ConsumerWidget, which adds a ref parameter to build.
ref.watch(provider)— subscribe and rebuild on change (use inbuild).ref.read(provider.notifier)— get the notifier to call methods (use in callbacks).
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('Count: $count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}watch vs read — The Migration Trap
The most common migration bug is using the wrong access method. Map the old API to the new one carefully:
context.watch<T>()→ref.watch(provider)(rebuilds the UI)context.read<T>()in a callback →ref.read(provider.notifier)(no rebuild)
Rule: never call ref.watch inside an onPressed callback — it will not behave as expected and can cause unnecessary rebuilds. Use ref.read for one-off actions, ref.watch for values displayed in the UI.
Step 4 — Migrating Async State (FutureProvider)
Legacy apps often load data inside a ChangeNotifier with manual isLoading/error booleans. Riverpod replaces all of that with AsyncValue and a FutureProvider — loading and error states are modeled for you.
The UI then uses AsyncValue.when to render data, loading, and error branches exhaustively.
final userProvider = FutureProvider<User>((ref) async {
final repo = ref.watch(userRepositoryProvider);
return repo.fetchCurrentUser();
});
// In a ConsumerWidget:
final asyncUser = ref.watch(userProvider);
return asyncUser.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, st) => Text('Error: $e'),
);Step 5 — Combining Providers (No More ProxyProvider)
In the old package, making one model depend on another required ProxyProvider and careful ordering. In Riverpod, a provider simply calls ref.watch on another provider inside its body. Dependencies are explicit, type-safe, and reactive.
Below, a derived provider recomputes automatically whenever cartProvider changes — no manual subscription, no notifyListeners chains.
final cartProvider =
NotifierProvider<CartNotifier, List<Item>>(CartNotifier.new);
// Derived state — recomputes when the cart changes
final cartTotalProvider = Provider<double>((ref) {
final items = ref.watch(cartProvider);
return items.fold(0.0, (sum, item) => sum + item.price);
});A Standalone Taste of Reactive Derivation in Dart
You do not need Flutter to understand Riverpod's core idea: derived values recompute from their inputs. This pure-Dart snippet models the same fold-the-cart logic used in cartTotalProvider, so you can run and verify the math an online judge would compute.
class Item {
final String name;
final double price;
Item(this.name, this.price);
}
double cartTotal(List<Item> items) =>
items.fold(0.0, (sum, item) => sum + item.price);
void main() {
final cart = [
Item('Coffee', 3.50),
Item('Bagel', 2.25),
Item('Juice', 4.00),
];
print('Items: ${cart.length}');
print('Total: \$${cartTotal(cart).toStringAsFixed(2)}');
}Step 6 — Incremental Migration Strategy
You rarely rewrite a whole app at once. A safe, incremental plan:
- Wrap once: add
ProviderScopeat the root immediately — it coexists with the oldproviderpackage. - Leaf-first: migrate self-contained features (settings, theme, counters) before tangled ones.
- Bridge if needed: a Riverpod provider can read legacy data, and a legacy widget can stay until its screen is converted.
- Convert widgets: change
StatelessWidget→ConsumerWidgetandStatefulWidget→ConsumerStatefulWidgetas you touch each screen. - Delete the old
ChangeNotifierProviderand theproviderdependency only when nothing references them.
Quick Check — watch vs read
Test your understanding of the most error-prone part of the migration.
Recap — From Provider to Riverpod
You migrated a legacy ChangeNotifier + Provider feature to Riverpod 2.0:
- Wrapped the app in a single ProviderScope instead of nested provider widgets.
- Turned
ChangeNotifierinto a Notifier with astatefield and nonotifyListeners(). - Swapped
context.watch/readforref.watch(display) andref.read(provider.notifier)(actions) inside a ConsumerWidget. - Modeled async with FutureProvider +
AsyncValue.when, and combined providers viaref.watchinstead ofProxyProvider. - Followed a leaf-first, incremental path so the two systems coexist during the transition.
The payoff: a compile-safe provider graph where a missing dependency is a build error, not a production crash.
Ucz się Dart dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 22
- Lekcje
- 88
Często zadawane pytania
Czy lekcja „Od Provider do Riverpod: migracja starszego zarządzania stanem” jest bezpłatna?
Tak — pełny tekst „Od Provider do Riverpod: migracja starszego zarządzania stanem” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Flutter Mobile Development, przejdź na CoddyKit PRO. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.
Co nauczysz się w „Od Provider do Riverpod: migracja starszego zarządzania stanem”?
Konwertuj istniejący kod ChangeNotifier i Provider na bezpieczny podczas kompilacji graf providerów Riverpod Ćwiczysz Flutter Mobile Development z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Flutter Mobile Development?
Nie wymagamy żadnego doświadczenia. Flutter Mobile Development w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Od Provider do Riverpod: migracja starszego zarządzania stanem”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Flutter Mobile Development?
Tak. Każda lekcja Flutter Mobile Development zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Od Provider do Riverpod: migracja starszego zarządzania stanem
- Generowanie kodu za pomocą riverpod_generator i @riverpod
- Potoki danych AsyncNotifier i FutureProvider
- Zakresy providerów, nadpisywanie i ProviderObserver