Moving from Riverpod 2 to 3
Riverpod 3 keeps most existing code working but brings a set of important changes. The key points from the docs' "what's new" page:
Legacy APIs moved to a separate import. StateProvider, StateNotifierProvider and ChangeNotifierProvider were moved to package:riverpod/legacy.dart to discourage their use. They keep working for backward compatibility.
Breaking changes:
- Refs and notifiers throw when accessed after disposal.
- Exceptions from providers are wrapped in
ProviderExceptionfor better debugging. AsyncValuebecame sealed;valueOrNullwas renamed tovalue.- All
updateShouldNotifyimplementations now use==comparison. AutoDisposeNotifierandFamilyNotifierwere merged into a singleNotifierclass.- A unified
Ref: provider-specific types such asFutureProviderRefwere removed.
New capabilities: automatic retry (with exponential backoff), Ref.mounted, pause/resume (subscriptions paused automatically for widgets that are not visible), the experimental Mutation mechanism and experimental offline persistence (with implementations such as riverpod_sqflite).
| Change | Practical impact |
|---|---|
| Legacy providers in a separate import | The code doesn't break but needs an added import — a signal to migrate gradually |
| `AsyncValue` became sealed | Pattern matching in a `switch` is checked exhaustively; `valueOrNull` must be renamed to `value` |
| Throwing on access after disposal | Previously silent mistakes now surface — new test failures may appear |
| Wrapping in `ProviderException` | Code that checks error types must be revisited |
| Automatic retry | The UI's behaviour on network errors changes; an error screen may appear later |
| Merged notifier classes | Classes written against `AutoDisposeNotifier`/`FamilyNotifier` must be updated |
// 1) valueOrNull → value
// v2:
final todos = ref.watch(todosProvider).valueOrNull;
// v3:
final todos = ref.watch(todosProvider).value;
// 2) Legacy provider-lər üçün import
// v3-də StateProvider/StateNotifierProvider/ChangeNotifierProvider:
import 'package:riverpod/legacy.dart';
// və ya yeni yanaşmaya keçid:
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
// 3) Sealed AsyncValue → tam pattern matching
return switch (ref.watch(todosProvider)) {
AsyncValue(:final value?) => TodoList(items: value),
AsyncValue(:final error?) => ErrorView(message: '$error'),
_ => const TodoSkeleton(),
};
// 4) Async-dan sonra provider-in hələ yaşadığını yoxlamaq
class Todos extends AsyncNotifier<List<Todo>> {
@override
Future<List<Todo>> build() => ref.watch(repositoryProvider).fetchAll();
Future<void> refreshQuietly() async {
final result = await ref.read(repositoryProvider).fetchAll();
if (!ref.mounted) return; // v3: söküldükdən sonra müraciət xəta atır
state = AsyncData(result);
}
}The four most common fixes during migration.
The surprising part of the migration is usually automatic retry: per the docs, when a provider throws during computation it is retried up to 10 times with an exponential backoff from 200 ms to 6.4 seconds. That can delay the appearance of an error screen when offline. The retry parameter (on a provider, ProviderScope or ProviderContainer) tunes the behaviour, and retry: (retryCount, error) => null disables it entirely. Worth knowing for tests with timing expectations.
📚 Sources and documentation
- What's new in Riverpod 3officialriverpod.dev
The official list of every change described in this topic.
- The 3.0 migration guideofficialriverpod.dev
The step-by-step upgrade instructions.
- Automatic retryofficialriverpod.dev
- package:flutter_riverpodofficialpub.dev
The current version and changelog — where version-dependent claims can be verified.