By Abdelrahman Saed — Senior Mobile Engineer. Last updated: 2026-08-10.
Use Riverpod. Provider is effectively legacy. Riverpod was written by the same author (Rémi Rousselet) specifically to fix Provider's design flaws — Provider.of runtime crashes, BuildContext coupling, and test friction. There is no scenario where a new Flutter project in 2026 should start with Provider.
If you inherit a Provider codebase, migrate incrementally — they coexist — but every new feature should be Riverpod. I would not greenlight a new project using Provider today, full stop.
| Feature | Riverpod | Provider |
|---|---|---|
| Resolution | Compile-time | Runtime (throws if missing) |
| BuildContext dependency | No | Yes (required for lookup) |
| Author | Rémi Rousselet | Rémi Rousselet (older) |
| Status | Active, recommended | Maintenance / legacy |
| Testing | ProviderContainer, no widget tree | Needs widget tree + MultiProvider |
| Code generation | Optional (riverpod_generator) | None |
| AutoDispose / family | First-class | Limited |
| Learning curve | Moderate | Low (but footguns) |
| Best for | New projects | Legacy maintenance only |
Provider was the first widely-adopted DI/state solution for Flutter, and the Flutter team even featured it in samples. But its reliance on BuildContext for lookups created real problems: runtime ProviderNotFoundException, untestable widgets without a tree of ancestors, and implicit dependencies. Rémi Rousselet wrote Riverpod to solve exactly these issues — it is Provider's deliberate successor.
Provider lookups happen at runtime:
final user = Provider.of<UserModel>(context); // throws if not found
Riverpod references are resolved at compile time through generated provider objects:
final user = ref.watch(userProvider); // resolved at compile time
If userProvider does not exist, the code does not compile. This alone eliminates a whole class of runtime crashes that plague Provider codebases.
Provider is bound to the widget tree — you need a BuildContext to read it, which means business logic in non-widget code has to thread context through or use workarounds. Riverpod providers are independent objects; you can read them in a repository, a test, or a background isolate. This makes the architecture cleaner and testing trivial.
Provider tests require building a ProviderScope/MultiProvider ancestor tree and pumping a widget. Riverpod tests create a ProviderContainer and read the provider directly — no widget tree needed:
final container = ProviderContainer();
expect(container.read(countProvider), 0);
For a team that cares about unit-testing business logic without a widget harness, this is a massive quality-of-life win.
Provider and Riverpod coexist fine — you can wrap the app in both MultiProvider and ProviderScope and migrate feature by feature. The risk of a big-bang rewrite is never worth it. Migrate the next feature you touch and let attrition do the rest.
Riverpod's DevTools integration shows a live provider graph — every provider, its current value, its dependencies, and which widgets are listening. When a provider rebuilds unexpectedly or you have a circular dependency, the graph view points you at the problem in seconds. Provider has no equivalent; you are back to debugPrint and Provider.of breakpoints. For a team that debugs state issues weekly, this is a meaningful day-to-day advantage that compounds as the provider graph grows.
// must provide in the tree
MultiProvider(
providers: [
Provider<AuthRepo>(create: (_) => AuthRepo()),
ChangeNotifierProvider<UserModel>(create: (_) => UserModel()),
],
child: MyApp(),
);
// reading — runtime lookup, throws if missing
final repo = context.read<AuthRepo>();
// declare providers anywhere
final authRepoProvider = Provider((ref) => AuthRepo());
final userModelProvider = NotifierProvider<UserNotifier, User>(UserNotifier.new);
// reading — compile-time resolved
Consumer(builder: (_, ref, __) {
final repo = ref.read(authRepoProvider);
});
// testable without a widget tree
void main() {
final container = ProviderContainer();
expect(container.read(authRepoProvider), isA<AuthRepo>());
}
The Riverpod version is testable in a pure Dart test, has no runtime lookup risk, and the provider graph is explicit. There is no contest for new work.
Choose Riverpod when: you are starting a new project or feature, you want compile-time safety and testability, or you are building an architecture where business logic lives outside widgets. This is the default for all new Flutter work.
Choose Provider when: you are maintaining an existing Provider codebase and a full rewrite is not justified. Wrap new features in Riverpod and migrate opportunistically. Do not start new projects with Provider.
Not marked deprecated on pub.dev, but it is in maintenance mode and the Flutter team and its own author recommend Riverpod for new work. Treat it as legacy for new project decisions.
Moderate. The concepts (providers, ref, autoDispose, family) take a day to internalize. Code generation with riverpod_generator reduces boilerplate further. It is far less steep than BLoC.
Yes. Wrap the app in both MultiProvider and ProviderScope. Migrate one feature at a time. The two do not conflict because Riverpod does not use the widget tree for lookups.
Start without it — learn the provider types, ref, autoDispose, and family by writing them longhand so the mental model sticks. Once that clicks, adopt riverpod_generator (@riverpod annotations) for new providers; it reduces boilerplate, gives you safer naming, and generates the boilerplate you would hand-write anyway. The generated API is identical to the manual one, so the knowledge transfers both ways.
InheritedWidget is the Flutter primitive both Provider and Riverpod build on, but it has real pain points: no compile-time safety, no auto-dispose, no family parameters, and lookups are O(1) but require a BuildContext. Provider wraps InheritedWidget to make it bearable; Riverpod abstracts it away entirely. For anything beyond passing a theme down the tree, reach for Riverpod instead of raw InheritedWidget.
Available for hire. Abdelrahman Saed is a Senior Mobile Engineer (Flutter) — open to full-time, fractional, contract, or advisory work. Hire me →
Book a 20-minute call · Download the CV (PDF) · See how I work