Provider Package → Riverpod
Migrate from the Flutter provider package to Riverpod by replacing BuildContext-based dependency lookup with provider-based dependency management.
What is it?
This migration replaces the traditional provider package with Riverpod.
Although both libraries solve similar problems, they have different architectures:
- Provider package relies on
BuildContextto access state. - Riverpod removes the dependency on
BuildContextand exposes state throughRefand providers.
Riverpod was designed to solve many limitations of the original provider package while keeping a familiar programming model.
Why does it exist?
The provider package works well for many applications, but it has several limitations:
- Dependencies on
BuildContext - Providers must exist above the widget in the tree
- Runtime errors when a provider cannot be found
- More difficult testing
- Less flexible provider composition
Riverpod addresses these issues by:
- Removing
BuildContextfrom provider access - Providing compile-time safety
- Improving testing
- Supporting provider composition
- Managing provider lifecycles automatically
Syntax
Provider package
final counter = context.watch<Counter>();
Text('${counter.count}');
Explanation:
context.watch()subscribes the widget to changes.- The provider must exist above this widget in the widget tree.
Riverpod
final counter = ref.watch(counterProvider);
Text('$counter');
Explanation:
ref.watch()subscribes to the provider.- No
BuildContextlookup is required.
Reading without listening
Provider package:
final counter = context.read<Counter>();
Explanation:
- Reads the current value without rebuilding.
Riverpod:
final counter = ref.read(counterProvider);
Explanation:
- Reads the provider without creating a subscription.
Application setup
Provider package:
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => Counter(),
),
],
child: const MyApp(),
),
);
Explanation:
- Providers are registered using
MultiProvider.
Riverpod:
runApp(
const ProviderScope(
child: MyApp(),
),
);
Explanation:
ProviderScopeis the root container for all Riverpod providers.- Individual providers do not need to be registered here.
Mental Model
Provider package:
Widget
│
BuildContext
│
Provider Tree
│
State
Riverpod:
Widget
│
WidgetRef
│
Provider
│
State
Riverpod separates state access from the widget tree, making providers easier to reuse and test.
Examples
Simple Example
Provider package:
final theme = context.watch<ThemeNotifier>();
Explanation:
- The widget rebuilds when the notifier changes.
Riverpod:
final theme = ref.watch(themeProvider);
Explanation:
- The widget watches a provider instead of a type in the widget tree.
Real-World Example
Provider package:
class UserRepository {
UserRepository(this.api);
final ApiClient api;
}
Provider(
create: (_) => ApiClient(),
),
Provider(
create: (context) {
return UserRepository(
context.read<ApiClient>(),
);
},
)
Explanation:
- Dependencies are resolved using
BuildContext.
Riverpod:
final apiProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
final repositoryProvider =
Provider<UserRepository>((ref) {
final api = ref.watch(apiProvider);
return UserRepository(api);
});
Explanation:
- Providers depend on other providers directly.
- Dependencies are explicit and easier to test.
When to Use
Migrate when you want:
- Better testability
- Compile-time safety
- Cleaner dependency injection
- Improved provider composition
- Better lifecycle management
- Modern Riverpod APIs like
NotifierandAsyncNotifier
When NOT to Use
Migration may not be necessary if:
- The application is small and stable.
- There are no plans for future development.
- Migration effort outweighs the benefits.
For actively maintained applications, Riverpod is generally the recommended choice.
Best Practices
- Migrate feature by feature.
- Replace
context.watch()withref.watch(). - Replace
context.read()withref.read(). - Replace
ChangeNotifierwithNotifierwhere appropriate. - Keep providers small and focused.
- Add tests during migration.
Common Mistakes
Trying to use BuildContext
Wrong:
context.watch<User>();
Explanation:
- Riverpod does not use
BuildContextto access providers.
Correct:
ref.watch(userProvider);
Explanation:
WidgetRefis the primary way to access providers in widgets.
Keeping ChangeNotifier everywhere
Wrong:
ChangeNotifierProvider<UserNotifier>(
create: (_) => UserNotifier(),
);
Explanation:
- This keeps the old Provider architecture.
Correct:
final userProvider =
NotifierProvider<UserNotifier, User>(
UserNotifier.new,
);
Explanation:
- Prefer Riverpod's native
NotifierAPIs for new code.
Registering every provider in one place
Wrong:
MultiProvider(
providers: [
...
],
)
Explanation:
- Riverpod providers are declared where they belong.
- They do not require centralized registration.
Correct:
const ProviderScope(
child: MyApp(),
);
Explanation:
ProviderScopemanages all providers automatically.
Related APIs
- ProviderScope
- Provider
- Notifier
- NotifierProvider
- AsyncNotifier
- AsyncNotifierProvider
- WidgetRef
ref.watch()ref.read()
Summary
Migrating from the provider package to Riverpod primarily involves replacing BuildContext-based state access with Ref-based provider access. Riverpod offers better testability, stronger type safety, improved dependency injection, and a more flexible architecture, making it a solid choice for modern Flutter applications.