We migrated about 15,000 lines from Provider to Riverpod over three weeks. The result was good, and I would still tell most teams reading this not to do it.
"Was it worth it?" is the wrong question, because the answer depends entirely on a codebase you have and I do not. The useful question is narrower: have you actually hit Provider's limits, or have you just read that Riverpod is better? This post is the honest version of that answer, including the parts that did not go well and the cases where staying put is correct.
Where Provider ran out
Provider is fine until dependencies start depending on each other. Ours did. A checkout flow sat on five of them: auth, preferences, inventory, payment and shipping.
ProxyProvider5<Auth, Prefs, Inventory, Payment, Shipping, Checkout>(
update: (_, auth, prefs, inventory, payment, shipping, __) =>
Checkout(auth, prefs, inventory, payment, shipping),
)
Two things about that line are worse than they look.
The first is that the number is part of the type. Adding a sixth dependency does not mean editing an argument list, it means changing ProxyProvider5 to ProxyProvider6 and rewriting the signature. The type system is counting for you, and the count is load bearing.
The second is ordering. Every one of those five providers has to appear above Checkout in the widget tree. Get that wrong and nothing complains at compile time. You find out at runtime, in the flow that takes people's money.

The listener tax
The other cost was quieter and did more damage. Every dependency a provider listened to needed two manual calls, in two different places:
// on init
auth.addListener(_update);
// on dispose, and forget this line at your peril
auth.removeListener(_update);
Five dependencies is roughly ten lines of listener bookkeeping per provider, written by hand, every single time. The addListener half is easy to remember because the feature does not work without it. The removeListener half is easy to forget because nothing breaks today. It leaks, quietly, and you find it weeks later while chasing something else.
That asymmetry is the real problem. The code that keeps you honest is exactly the code with no immediate feedback when it is missing.

What ref.watch replaces
The same five dependencies in Riverpod, with none of the wiring:
final checkoutProvider = Provider((ref) {
final auth = ref.watch(authProvider);
final prefs = ref.watch(prefsProvider);
final inventory = ref.watch(inventoryProvider);
final payment = ref.watch(paymentProvider);
final shipping = ref.watch(shippingProvider);
return Checkout(auth, prefs, inventory, payment, shipping);
});
ref.watch subscribes and unsubscribes for you. There is no numbered type to grow, no addListener, no dispose to forget. Providers are declared at the top level rather than mounted in the widget tree, so ordering stops being something you can get wrong: a provider is resolved when it is read, not when it happens to sit in the tree.
The win here is not that the code is shorter, though it is. It is that a whole category of mistake stops being expressible.
Testing stops being setup
This was the change I underestimated. Testing a Provider-based service meant building all six objects by hand, injecting each one, running the test, and disposing all six by hand afterwards. Most of the file was scaffolding.
final container = ProviderContainer(
overrides: [
authProvider.overrideWithValue(mockAuth),
paymentProvider.overrideWithValue(mockPayment),
],
);
addTearDown(container.dispose);
Override what the test cares about, let the rest resolve normally, and dispose once. Our test setup went from about 30 seconds to about 5. That number is setup time, not suite time, and it matters more than it sounds: setup cost is what decides whether people write the test at all.
What three weeks bought
The honest accounting, for our codebase:
| Provider | Riverpod | |
|---|---|---|
| Lines to wire five dependencies | ~35 | ~10 |
| Listener management | manual | automatic |
| Memory leak risk | high | none |
| Test setup | ~30s | ~5s |
Removed along the way: roughly 847 lines of listener code, 12 ProxyProviders, and 3 leaks nobody knew were there. The leaks are the line I would underline. They were not found by profiling or by a bug report. They fell out of a mechanical migration, which means they had been shipping for a long time.
Net effect was around 40% less boilerplate in that layer and a test suite that runs about twice as fast.
Worth it is not the same as painless
Learning Riverpod took about an hour. The API is small and it is well designed, and if you have used Provider you already understand most of it.
Migrating 15,000 lines took three weeks. That is the number that matters, and it is the one people leave out of migration posts. The time did not go on learning. It went on rewiring dependencies, on the tests that broke because they were coupled to the old construction order, and on the long tail of places where the old pattern had been quietly worked around rather than used properly.
What scales with your codebase is the migration, not the learning curve. Anyone can read the docs in an afternoon. Nobody can rewire a large app in one.
Riverpod also offers optional code generation. It is a separate decision from the migration itself, and it adds a build step to configure and keep running, so decide it on its own merits rather than inheriting it with the move.
Do not migrate for hype
Riverpod is better than Provider on the axes above. That does not make migrating correct for you. Skip it if any of these are true:
- You have fewer than five providers. Nearly every advantage above is about dependencies between providers. With a handful of independent ones, you are buying a solution to a problem you do not have.
- Provider already works for your team. Working code that the whole team understands is worth more than a better abstraction that half of them are still learning.
- You are shipping in two weeks. A three week migration is not a two week migration done carefully. Ship first.
The version of this decision that ages well is not "which library is better". It is whether you have hit the specific wall this move gets you over: providers that depend on providers, listener bookkeeping you are maintaining by hand, and test setup expensive enough that people skip writing tests.
If you have hit that wall, the three weeks pay for themselves. If you have not, you are about to spend three weeks proving that your current code was fine.
