Flutter Riverpod
Riverpod 3 declares providers outside the widget tree, reads them through Ref, and caches, disposes and retries them automatically. Interviewers ask what it fixes about Provider.
on this pageshowhide
guide
overview
~1 minRiverpod is a state-management and dependency-injection library for Flutter and Dart, written by the author of Provider as its successor. Its state lives in provider objects declared at the top level of a file, outside the widget tree, and code reaches them through a `Ref`. Interviewers use it to test whether you can reason about *where state lives, who depends on it and when it goes away* — and the standard opening question is what Riverpod fixes about Provider: no runtime lookup failure because a provider sits above the wrong widget, no dependence on `BuildContext` for reading state, and several providers of the same type without wrapper classes. The hub follows the life of one piece of state. [Declared provider types](/topics/mob-riverpod-providers) is the vocabulary: which kind of provider fits which job. [Ref watch, read and listen](/topics/mob-riverpod-ref) is how widgets and other providers consume them. [Auto-dispose and families](/topics/mob-riverpod-modifiers) decides how long state survives and how it is keyed. [AsyncValue states](/topics/mob-riverpod-async) is how loading, data and failure reach the screen. [Annotation codegen](/topics/mob-riverpod-codegen) is the `@riverpod` syntax that generates providers for you, and [overrides and containers](/topics/mob-riverpod-testing) is how all of it gets tested. Junior rounds ask you to pick a provider type and explain `watch` against `read`. Middle and senior rounds move to cache lifetimes, refresh behaviour, retries, scoped overrides and migrating a Riverpod 2 codebase. Learn the provider types and `Ref` first; everything else modifies them.
primer
### Providers are declarations, not widgets A provider is a final, usually global, object that describes how to create a value. Declaring it creates nothing: the value comes into being the first time something reads it, inside a **container** that holds every provider's state. In Flutter that container comes from the `ProviderScope` at the root of the app. Because the declaration is plain Dart, a missing provider becomes a compile error instead of a runtime exception, and two providers returning the same type never collide. ### Dependencies form a graph A provider can watch other providers while it builds, so the app's state becomes a graph of derived values. When an upstream value changes, Riverpod rebuilds what depends on it and nothing else. Most design answers come down to drawing that graph: what is the source of truth, what is derived, and which edge causes a rebuild. ### Lifetime is explicit Every provider has a lifetime you choose. **Auto-dispose** ties state to its listeners, which frees memory but can surprise you with a refetch; **keeping alive** trades that back for a cache. A **family** multiplies one declaration into an instance per argument, which makes argument equality a correctness concern rather than a detail. ### Async is a value, not a callback Async providers do not make the UI await a `Future`. They expose an **`AsyncValue`**, a sealed type that is loading, data or error — and can carry the last good data while a new load runs. Screens render from that one value, which is why exhaustive handling and refresh semantics come up so often. ### Mutation goes through notifiers Read-only providers compute; **notifiers** own state that changes. A notifier's `build` produces the starting state and its methods replace it, so every change passes through one class that tests and other providers can reason about. ### Tests swap the graph, not the code Any provider can be **overridden** in a container or scope. A test replaces a repository at the edge of the graph and every provider above it sees the fake, while production code stays untouched and no separate injection framework is needed.
- Provider
- A top-level object that declares how to create one piece of state or one dependency. It holds no value itself; a container creates and caches the value on first read.
- ProviderScope
- The Flutter widget, placed at the root of the app, that owns the provider container. A nested scope can override providers for one subtree.
- ProviderContainer
- The object that stores provider states and their dependency graph. Flutter apps get one from ProviderScope; pure Dart code and unit tests create one directly.
- Ref
- The handle a provider or notifier receives to read other providers, register dispose callbacks and control its own lifetime.
- WidgetRef
- The Flutter-side counterpart of Ref, handed to consumer widgets so their build methods and handlers can read providers.
- Notifier
- A class that owns mutable provider state: build returns the initial state, and its methods assign new state. AsyncNotifier is the async variant.
- AsyncValue
- A sealed type with AsyncData, AsyncLoading and AsyncError cases, used by async providers so the UI can render every state from one value.
- autoDispose
- A modifier that destroys a provider's state once nothing listens to it any more, so the next read builds it from scratch.
- keepAlive
- A way to exempt a provider from auto-disposal, either permanently in its declaration or conditionally from inside its build.
- Family
- A provider that takes an argument and keeps a separate state per distinct argument value, such as one product per id.
- Override
- A replacement for a provider's creation logic or value inside one container or scope, used for tests, previews and subtree-specific values.
- riverpod_generator
- The build_runner code generator that turns functions and classes annotated with @riverpod into providers, deriving their names and kinds.
Start from the root. `ProviderScope` wraps the app and owns one container. When a consumer widget calls `ref.watch` on a provider for the first time, the container runs that provider's build, and the build may itself watch further providers — a repository, a settings value, an auth state. Each watch records an edge in the graph. When a source changes, the container marks its dependants dirty and rebuilds them lazily, and widgets that watch the result rebuild with them. When the last listener of an auto-dispose provider goes away, its state is dropped and its dispose callbacks run. In that picture, provider types decide what a node holds, `Ref` draws the edges, modifiers decide when a node is created, keyed and freed, `AsyncValue` is what async nodes carry to the widget, and overrides replace a node before anything reads it. Codegen only writes the declarations; the runtime graph it produces is the same. The pieces meet in a few lines — a repository at the edge, a notifier that depends on it, and a test that swaps the edge: ```dart final repoProvider = Provider<TodoRepo>((ref) => HttpTodoRepo()); class Todos extends AsyncNotifier<List<Todo>> { @override Future<List<Todo>> build() => ref.watch(repoProvider).fetchAll(); } final todosProvider = AsyncNotifierProvider<Todos, List<Todo>>(Todos.new); // in a test final container = ProviderContainer.test( overrides: [repoProvider.overrideWithValue(FakeTodoRepo())], ); ``` Nothing in `Todos` knows about the fake. That is the property interviewers look for: the dependency is declared once, resolved by the container, and replaceable wherever the graph is built.
- Declared Provider Types →
Start with the kinds of provider and the job each fits; every other section assumes you can pick one.
- Ref Watch, Read & Listen →
How widgets and providers consume state: watch, read and listen, and where each one belongs.
- AsyncValue States →
Most real providers load data, so learn how loading, data and error states reach the screen.
- Auto-Dispose & Families →
Lifetime and keying: auto-dispose, keep-alive caches and families, where leaks and surprise refetches come from.
- Overrides & Containers →
Overrides and containers show why the provider graph makes Riverpod code testable without a widget tree.
- Annotation Codegen →
The @riverpod syntax generates what the earlier sections taught by hand; learn it once the runtime model is clear.
Calling
ref.readinside a build method to avoid rebuilds: the widget then shows stale state, because read never subscribes.Doing setup in a notifier's constructor instead of
build, whererefandstateare not yet available.Passing a freshly created list or object as a family argument, so every call misses the cache and builds new state.
Being surprised by a refetch after navigating back: auto-dispose dropped the state when the screen's last listener went away.
Rendering only the data case of an
AsyncValue, or blanking the screen during a refresh that still holds the previous data.Overriding a provider in a nested
ProviderScopewithout declaring dependencies, so providers that watch it never see the override.Answering with Riverpod 2 idioms such as
StateNotifierProviderwithout saying Riverpod 3 moved them to the legacy import.
This guide assumes **Riverpod 3**, where the answers below are written. Many codebases in interviews are still on Riverpod 2, so it helps to know what moved: - **Riverpod 2** introduced `Notifier` and `AsyncNotifier` and the `@riverpod` code generator, and made them the recommended way to hold mutable state. - **Riverpod 3** keeps `StateProvider`, `StateNotifierProvider` and `ChangeNotifierProvider` only as legacy APIs behind a separate import, and expects new code to use notifiers. - **Riverpod 3** added behaviour you may be asked to explain in an incident story: providers that fail while building are retried automatically, and listeners of widgets that are not visible are paused instead of notified. - **Riverpod 3** also shipped experimental APIs, including mutations, which let the UI show whether a save or delete is still running. Treat anything marked experimental as subject to change and say so in an answer. Later 3.x releases changed tooling and testing details, such as how `riverpod_lint` is enabled and how a notifier family is overridden. When an answer depends on one of those, name the minor version.
Riverpod is usually weighed against three neighbours. **Provider**, from the same author, wraps `InheritedWidget` and looks providers up through `BuildContext`; Riverpod exists to remove that coupling. **Bloc** models state as streams of events and states with explicit classes for each, which suits teams that want a strict, ceremony-heavy pattern. Plain `setState` and `InheritedWidget` remain the right answer for state local to one widget. A strong answer names the trade: Riverpod gives dependency injection and caching in one tool, but you pay for it with a dependency graph you have to understand. Around it sit the packages it is commonly used with. `flutter_riverpod` is the Flutter binding and `hooks_riverpod` adds `flutter_hooks` support. `riverpod_generator` and `riverpod_lint` form the codegen and analysis side, and projects that already run `build_runner` for `freezed` or `json_serializable` often adopt the generator alongside them.
explore
- Declared Provider Types5 questions
- Ref Watch, Read & Listen5 questions
- Auto-Dispose & Families5 questions
- AsyncValue States5 questions
- Annotation Codegen6 questions
- Overrides & Containers5 questions
questions
page 2 of 2In Riverpod 3.2 and later, why was a Notifier family's overrideWith deprecated for overrideWith2, and when do you override one member instead?
basics
~20 soverrideWith2, added in Riverpod 3.2, passes the family argument to the callback that builds a fake notifier, which the deprecated family overrideWith could not. Override one member, provider(arg).overrideWith, when only that argument should be fake.
showing 31–31 of 31