flutter-riverpod3
Skill10 starsChanged 42 days agoMITView on GitHub
Use when writing or reviewing Riverpod 3 code in a Flutter app WITHOUT code generation - choosing a provider type, wiring dependency injection, writing Notifier or AsyncNotifier classes, using ref.watch / read / listen / select, handling disposal, mounted checks and automatic retry, or when someone proposes riverpod_generator, @riverpod, StateNotifier, Bloc, GetIt or the provider package.
In this repository5 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 89
Verify the retry parameter against your Riverpod version.
- Very specific: line 119
Use selectAsync to derive from another provider's *data* without awaiting the whole AsyncValue.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Riverpod 3 without code generation
Riverpod is the only tool for dependency injection, state management and async orchestration. Providers are declared explicitly by hand.
Rules and why
| Rule | Why |
No riverpod_generator, riverpod_annotation, @riverpod | One less build_runner target, the dependency graph is readable in plain Dart, and nothing to migrate when the generator's output changes. (build_runner may still run for json_serializable etc.) |
No StateNotifier / StateNotifierProvider | Legacy API. Notifier / AsyncNotifier replace it. |
No Bloc, Cubit, provider package, GetIt, Injectable | A second state or DI system doubles what a reader must learn and splits the graph. |
Every dependency is exposed through a Provider | One graph, one override mechanism for tests. |
| Never instantiate dependencies inside widgets | A widget that does Dio() or Repo() cannot be tested or swapped. |
Pick the provider by role
| Need | Use |
| Immutable dependency (repository, service, client) | Provider<Contract> |
| Async read-only data | FutureProvider |
| Continuous stream (auth state, connectivity) | StreamProvider |
| Mutable synchronous state (filters, selected tab, a form) | NotifierProvider |
| Mutable asynchronous state (a screen's store, a session) | AsyncNotifierProvider |
| One user action in flight | SubmissionNotifier + autoDispose (see flutter-result-errors) |
One responsibility per provider. Name them xProvider / XNotifier.
final todayProvider = FutureProvider<Today>((ref) async {
final result = await ref.watch(todayRepositoryProvider).getToday();
return result.getOrThrow(); // the sanctioned throw: becomes AsyncError
});watch, read, listen
| Where | Use | Never |
Provider body, Notifier.build, widget build | ref.watch | ref.read for data that can change: the consumer will not update |
Callbacks, notifier methods, onPressed | ref.read | ref.watch: it throws or leaks |
| Side effects (navigation, snackbar, analytics) | ref.listen in a listener widget (see flutter-ui-side-effects) | Doing them inside build |
State classes
- State is immutable and extends
Equatable. Riverpod only notifies when the new state is not==to the old one, andselectcompares by value, so value equality is what makes both work as intended. - Mutate with
state = AsyncData(current.copyWith(...)); never mutate a list in place (the reference does not change, so nothing fires). - On a failed reload or load-more, keep the previous state and drop only the spinner. Blanking what the user is reading is worse than a stale list.
- Mutating methods return
AsyncResultso the caller can report the failure; the store itself changes only on success, so a failed attempt never wipes app-wide state. Template:assets/async_store_notifier.dart.
Lifecycle
autoDisposefor screen-scoped state and for per-attempt submissions, so a stale error resets when the screen is left.- Never
autoDisposea long-lived singleton that is read from callbacks (a text-to-speech engine, a socket): it can be torn down mid-use and crash on the next event. Release native resources withref.onDispose. ref.onDisposefor every timer, debouncer and stream subscription a provider creates.if (!ref.mounted) return;after everyawaitin a notifier that can be disposed while a request is in flight; assigningstateafterwards throws. Still hand the result back to the caller.ref.invalidate(provider)is the retry button: wireonRetryto it.
Automatic retry (Riverpod 3)
A provider that throws during build is retried automatically with
exponential backoff. With Result-based errors that is usually wrong:
an AuthFailure or ValidationFailure fails identically every time, and
calling a store or payment SDK ten more times is harmful.
- Install one policy at the composition root that retries only
NetworkFailureand 5xx, a couple of times:assets/retry_policy.dart. - Opt out per provider with
retry: (_, _) => nullwhen the work is not safe to repeat. - Tests switch retry off (see
flutter-testing).
Verify the retry parameter against your Riverpod version.
Composition root
- A required config provider throws until overridden, so a missing override fails at startup rather than mid-session.
- An optional capability defaults to a disabled/no-op config so tests never initialise a platform SDK.
- Build the
ProviderContaineryourself and useUncontrolledProviderScopewhen something must read providers before the first frame, or when a widget above the router needs the router instance. Seeassets/bootstrap_example.dart. - A widget placed above the router cannot use
context.router; read the router from a provider instead.
Rebuild performance
Measure first. These techniques add complexity; use them when a rebuild is observably expensive (a long list, a chart, dropped frames), not on suspicion.
- Push the
watchdown. A smallConsumerWidgetaround the subtree that needs the state beats aselectat the page root. Listener widgets exist for the same reason: they listen without watching. - Keep transient state out of app-wide providers. A submission in flight
belongs in its own
autoDisposeprovider, not in the session, or every screen watching the session rebuilds on every tap. selectthe narrowest thing.
Usefinal name = ref.watch(authProvider.select((s) => s.value?.user?.name ?? '')); final submitting = ref.watch(submissionProvider.select((s) => s.isLoading));selectAsyncto derive from another provider's data without awaiting the wholeAsyncValue.constthe leaves.
select rules: the selected value must be immutable and compared by value;
do not select a property that changes as often as its parent.
Anti-patterns
ref.readinsidebuildfor reactive data- A provider that holds business rules which belong in the domain
- Reading
failure.messagefor UI copy (seeflutter-result-errors) - Catching an error inside a provider to swallow it: fold the
Resultinstead - A single giant provider that every screen watches
- Creating a
ProviderContainerinside a widget
Review checklist
- No generator, annotation,
StateNotifier, Bloc, GetIt,providerpackage - Each provider has one job and the right type for it
watchin builds,readin callbacks,listenfor side effects- State classes are immutable and
Equatable autoDisposewhere scoped; never on long-lived singletonsref.mountedchecked after awaits;ref.onDisposefor timers and subscriptions- A retry policy is installed; unsafe providers opt out
- Required config providers throw until overridden
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

