Riverpod 2 → Riverpod 3
Migrate from Riverpod 2 to Riverpod 3 by adopting the new APIs, improved lifecycle behavior, and modern state management patterns.
What is it?
Riverpod 3 is the next major version of Riverpod. It builds on the foundation of Riverpod 2 while improving performance, consistency, and developer experience.
Most applications can migrate incrementally because Riverpod 3 maintains compatibility with many existing APIs. However, it also encourages replacing older patterns with newer, more idiomatic ones.
Why does it exist?
Riverpod 3 introduces improvements to:
- Provider lifecycle management
- Performance
- Code generation
- Modern notifier APIs
- Developer ergonomics
- API consistency
It also continues the transition away from older APIs such as StateNotifierProvider and ChangeNotifierProvider in favor of Riverpod's native notifier system.
Migration Checklist
For most applications, migration consists of the following steps:
- Upgrade to the latest Riverpod packages.
- Read the official migration guide.
- Replace deprecated APIs.
- Prefer
Notifier,AsyncNotifier, andStreamNotifier. - Update generated code (if using code generation).
- Run tests after each migration step.
- Migrate feature by feature instead of rewriting the entire application.
Syntax
Upgrade package versions
dependencies:
flutter_riverpod: latest
riverpod_annotation: latest
dev_dependencies:
riverpod_generator: latest
build_runner: latest
Explanation:
- Upgrade all Riverpod packages together.
- Keep generator packages compatible with the runtime package.
Replace StateNotifier
Before:
final counterProvider =
StateNotifierProvider<
CounterNotifier,
int>(
(ref) => CounterNotifier(),
);
Explanation:
- Uses the legacy
StateNotifierAPI.
After:
final counterProvider =
NotifierProvider<
CounterNotifier,
int>(
CounterNotifier.new,
);
Explanation:
NotifierProvideris the preferred API for synchronous state.
Replace FutureProvider when mutations are needed
Before:
final userProvider =
FutureProvider<User>((ref) async {
return repository.fetchUser();
});
Explanation:
- Suitable only for read-only asynchronous state.
After:
final userProvider =
AsyncNotifierProvider<
UserNotifier,
User>(
UserNotifier.new,
);
Explanation:
AsyncNotifiersupports loading, refreshing, and updating state.
Replace ChangeNotifierProvider
Before:
final settingsProvider =
ChangeNotifierProvider<SettingsNotifier>(
(ref) => SettingsNotifier(),
);
Explanation:
- Relies on Flutter's
ChangeNotifier.
After:
final settingsProvider =
NotifierProvider<
SettingsNotifier,
Settings>(
SettingsNotifier.new,
);
Explanation:
- Prefer Riverpod's native notifier APIs for new code.
Mental Model
Riverpod 2:
Legacy APIs
│
▼
StateNotifier
ChangeNotifier
Riverpod 3:
Modern APIs
│
▼
Notifier
AsyncNotifier
StreamNotifier
The goal is not just to upgrade versions, but to adopt the newer APIs that integrate more closely with Riverpod's architecture.
Examples
Simple Example
Before
class CounterNotifier
extends StateNotifier<int> {
CounterNotifier() : super(0);
}
Explanation:
- State is initialized in the constructor.
After
class CounterNotifier extends Notifier<int> {
@override
int build() => 0;
}
Explanation:
build()replaces constructor-based initialization.
Real-World Example
Before
class UserNotifier
extends StateNotifier<User?> {
UserNotifier() : super(null);
}
Explanation:
- Uses the legacy notifier pattern.
After
class UserNotifier
extends AsyncNotifier<User?> {
@override
Future<User?> build() async {
return repository.currentUser();
}
}
Explanation:
- Initialization, dependencies, and asynchronous loading are handled in one place.
When to Use
Migrate to Riverpod 3 when:
- Starting a new project.
- Maintaining an active application.
- Adopting modern Riverpod APIs.
- Improving testability and maintainability.
- Reducing boilerplate.
When NOT to Use
A complete rewrite is rarely necessary.
For large applications:
- Upgrade package versions first.
- Keep working code running.
- Migrate feature by feature.
- Replace legacy APIs gradually.
Incremental migration is usually safer than a full rewrite.
Best Practices
- Migrate one feature at a time.
- Prefer
Notifier,AsyncNotifier, andStreamNotifier. - Remove deprecated APIs gradually.
- Keep tests passing throughout the migration.
- Update generated code after changing annotated providers.
- Read release notes before upgrading across major versions.
Common Mistakes
Rewriting the entire project
Wrong:
Delete everything
Start over
Explanation:
- Full rewrites are risky and time-consuming.
- They often introduce new bugs.
Correct:
Upgrade packages
↓
Migrate one feature
↓
Run tests
↓
Repeat
Explanation:
- Incremental migration reduces risk and keeps the application functional.
Mixing old and new patterns unnecessarily
Wrong:
New Feature → StateNotifier
Another Feature → Notifier
Explanation:
- Continuing to build new features with legacy APIs creates inconsistency.
Correct:
Existing Features → StateNotifier (until migrated)
New Features → Notifier
Explanation:
- Existing code can remain stable while new development adopts modern APIs.
Ignoring generated code
Wrong:
Modify annotated providers
Run application
Explanation:
- Generated files become outdated.
Correct:
Modify providers
↓
Run build_runner
↓
Run application
Explanation:
- Keep generated code synchronized with source files.
Related APIs
- Notifier
- AsyncNotifier
- StreamNotifier
- NotifierProvider
- AsyncNotifierProvider
- StreamNotifierProvider
- StateNotifier
- StateNotifierProvider
- ChangeNotifierProvider
Summary
Migrating from Riverpod 2 to Riverpod 3 is primarily about adopting Riverpod's modern APIs rather than rewriting your application. Upgrade your packages, migrate incrementally, replace legacy notifiers with Notifier, AsyncNotifier, and StreamNotifier, and keep your codebase consistent as you transition to the latest version.