Why can Riverpod not throw the provider package's ProviderNotFoundException, and how does it replace provider's placement-based disposal?
answer
- widgets versus global declarations
- tree position versus a reference
- one ProviderScope at the root
- autoDispose instead of unmount
- family for per-page state
basics
~20 sprovider's providers are widgets found by type at runtime, so a misplaced one fails the lookup. Riverpod's are global final declarations read by reference from one root ProviderScope, and state is freed by autoDispose rather than by unmounting a widget.
solid answer
~40 sThe provider package wraps `InheritedWidget`: a provider is a widget, and a read searches the caller's ancestors by type, so whether it succeeds depends on tree position and is only known at runtime; hence `ProviderNotFoundException` on pushed routes, dialogs and refactors. Riverpod's providers are plain Dart objects declared as global `final` variables, and widgets read them by **reference**, `ref.watch(surveyAnswersProvider)`, from state held in a single `ProviderScope` above the app. There is no per-provider placement to get wrong; Riverpod's docs say it simply can't throw this exception, though forgetting the root `ProviderScope` raises a `StateError`. Disposal changes too: provider frees state when its widget unmounts, so you scope by placement; Riverpod frees an autoDispose provider when nothing listens anymore (the default for generated providers) and uses family for per-page state.
code
dart · 21 linesimport 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// A global declaration: any widget can reference it, no placement needed.
final surveyTitleProvider = Provider<String>(
isAutoDispose: true,
(ref) => 'Onboarding survey',
);
void main() {
runApp(const ProviderScope(child: MaterialApp(home: ReviewPage())));
}
class ReviewPage extends ConsumerWidget {
const ReviewPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Text(ref.watch(surveyTitleProvider));
}
}go deeper
Recall that provider's providers are widgets in the tree while Riverpod's are global variables read by reference.
Explain why tree-position lookup fails at runtime and reference lookup fails at compile time, and contrast unmount-driven with listener-driven disposal.
Map provider scoping patterns to Riverpod equivalents (autoDispose, family) and discuss the remaining ProviderScope failure honestly.
Weigh an incremental migration: run both libraries side by side, move features with the worst placement bugs first, and budget for retraining.
## Two ways to find a provider **provider** (the package) is, in its successor's words, an `InheritedWidget` wrapper. Each provider is a **widget** in the tree, and `context.watch<T>()` asks the framework for the nearest ancestor exposing type `T`. Whether that succeeds depends on where the reading widget sits relative to the provider, and the compiler cannot check it. The result is `ProviderNotFoundException` at runtime whenever a reader ends up outside the provider's subtree: a pushed route, a dialog on the root navigator, a refactor that moves a widget, or a test that forgot to pump a provider. **Riverpod** declares providers as **global `final` variables**; they are plain Dart objects, not widgets. A widget reads one by **referencing the declaration**, for example `ref.watch(surveyAnswersProvider)`, and the state lives in a container held by one `ProviderScope` placed above the whole app. Because the reference is an ordinary Dart identifier, a missing provider is a compile error, and there is no per-provider position to get wrong. Riverpod's migration docs say the exception 'was one of the main reasons Riverpod was created' and that Riverpod 'simply can't throw this exception'. The remaining failure mode is at the root: running without a `ProviderScope` makes Riverpod throw `StateError('No ProviderScope found')`. That is one check at app start, not one per provider per route. | Concern | provider package | Riverpod 3 | |---|---|---| | What a provider is | a widget | a global declaration | | How a reader finds it | by type, walking up the tree | by reference to the declaration | | Failure when misplaced | `ProviderNotFoundException` at runtime | not possible; missing root scope is a `StateError` | | Two providers of one type | inner shadows outer | independent declarations | | When state is freed | provider widget unmounts | autoDispose provider has no listeners | | Per-page state | place a provider on the page | a family provider keyed by a parameter | ## Disposal: placement versus listening In provider, the only disposal trigger is the provider widget leaving the tree, so developers **scope providers by placement** to control lifetime: put the survey's provider on the survey page and its answers die with the page. That breaks down when state must outlive one page or be shared across routes, which is exactly the pushed-route problem. Riverpod decouples lifetime from tree position: - **autoDispose**: Riverpod counts listeners; when the count reaches zero it waits one frame and, if still unused, destroys the state and runs `ref.onDispose` callbacks. It is on by default for code-generated providers and opt-in with `isAutoDispose: true` otherwise. - **family**: a parameterised provider creates one state per parameter, the replacement for 'custom state per page'. - Riverpod's own docs say scoping providers is 'kind-of discouraged' in favour of these two. ## Other consequences of being a widget The same root cause, providers being `InheritedWidget`s, explains other differences Riverpod's migration guide lists: - **One value per type in scope.** Two provider-package providers of the same type shadow each other, so developers wrap values in distinct types; two Riverpod declarations of the same type are simply two variables. - **Tests.** With provider, every widget test must re-create the providers above the widget under test. Riverpod providers exist by default, and tests replace them through overrides on the root scope. - **Reacting to changes outside build.** An `InheritedWidget` has no change callback, so side effects such as showing a snackbar on a state change need extra plumbing with provider; Riverpod offers a listen API for it. ## What this means in an interview 1. Name the mechanism: provider relies on tree position; Riverpod relies on a reference and a root container. 2. Name the consequence: provider's error is a runtime placement error; Riverpod moves it to compile time. 3. Name the disposal difference: unmount-driven versus listener-driven. 4. Keep the comparison fair: provider is simpler, has a small API and is fine for small apps with shallow navigation; its placement discipline is learnable. ## Migration notes - Riverpod can run beside provider during an incremental migration, one feature at a time. - Screen-scoped provider state usually maps to an autoDispose provider, and page-parameterised state to a family. - Choosing between the two libraries for a new project is a separate decision, weighed with team experience and app size.
- Can a Riverpod app still fail at startup because of provider setup?Yes, in one place: if the app is not wrapped in a `ProviderScope`, reading a provider throws a `StateError` with 'No ProviderScope found'. That is a single root-level mistake, found on the first run, rather than a per-route placement error that surfaces only on a particular navigation path.
- How would you port a survey provider scoped to one page into Riverpod?Declare it globally and enable automatic disposal, so its state is destroyed once the survey's widgets stop listening. If each survey needs its own state, make it a family keyed by the survey id. The pushed review page can then read it directly without re-providing anything.
provider is like asking the neighbours on your street for a tool: it works only if someone up your road has one, and moving house can break it. Riverpod is like a shared address book: you look the tool up by name, and it is there wherever you live.
saying these in an interview costs you the question
- Riverpod avoids the error by catching ProviderNotFoundException internally
- Riverpod providers must still be placed above each route that reads them
- provider frees state when the last listener stops watching
- Riverpod can never throw any error about a missing scope
- Two provider-package providers of the same type are independent