Skip to content
Back to skills

Flutter Riverpod3

ASecurity

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.

  • 6 stars
  • 0 votes
  • 1 copy
  • 1 view
  • Added October 3, 2026
developmentreacttestingapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 3, 2026

npx -y skills add Ethiel97/flutter-clean-riverpod-skills --skill flutter-riverpod3 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Flutter Riverpod3?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Flutter Riverpod3
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ethiel97-flutter-riverpod3/badge)](https://www.skillsdirectory.com/skills/ethiel97-flutter-riverpod3)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: flutter-riverpod3
description: 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.
---

# 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`.

```dart
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, and `select` compares 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 `AsyncResult` so 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

- **`autoDispose`** for screen-scoped state and for per-attempt submissions, so
  a stale error resets when the screen is left.
- **Never `autoDispose`** a 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 with `ref.onDispose`.
- **`ref.onDispose`** for every timer, debouncer and stream subscription a
  provider creates.
- **`if (!ref.mounted) return;` after every `await`** in a notifier that can be
  disposed while a request is in flight; assigning `state` afterwards throws.
  Still hand the result back to the caller.
- `ref.invalidate(provider)` is the retry button: wire `onRetry` to 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
  `NetworkFailure` and 5xx, a couple of times: `assets/retry_policy.dart`.
- Opt out per provider with `retry: (_, _) => null` when 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 `ProviderContainer` yourself and use `UncontrolledProviderScope`
  when something must read providers before the first frame, or when a widget
  above the router needs the router instance. See `assets/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.

1. **Push the `watch` down.** A small `ConsumerWidget` around the subtree that
   needs the state beats a `select` at the page root. Listener widgets exist
   for the same reason: they listen without watching.
2. **Keep transient state out of app-wide providers.** A submission in flight
   belongs in its own `autoDispose` provider, not in the session, or every
   screen watching the session rebuilds on every tap.
3. **`select` the narrowest thing.**
   ```dart
   final name = ref.watch(authProvider.select((s) => s.value?.user?.name ?? ''));
   final submitting = ref.watch(submissionProvider.select((s) => s.isLoading));
   ```
   Use `selectAsync` to derive from another provider's *data* without awaiting
   the whole `AsyncValue`.
4. **`const` the 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.read` inside `build` for reactive data
- A provider that holds business rules which belong in the domain
- Reading `failure.message` for UI copy (see `flutter-result-errors`)
- Catching an error inside a provider to swallow it: fold the `Result` instead
- A single giant provider that every screen watches
- Creating a `ProviderContainer` inside a widget

## Review checklist

- [ ] No generator, annotation, `StateNotifier`, Bloc, GetIt, `provider` package
- [ ] Each provider has one job and the right type for it
- [ ] `watch` in builds, `read` in callbacks, `listen` for side effects
- [ ] State classes are immutable and `Equatable`
- [ ] `autoDispose` where scoped; never on long-lived singletons
- [ ] `ref.mounted` checked after awaits; `ref.onDispose` for timers and subscriptions
- [ ] A retry policy is installed; unsafe providers opt out
- [ ] Required config providers throw until overridden

Files in this skill

  • SKILL.md7.2 KB
  • assets/async_store_notifier.dart3.2 KB
  • assets/bootstrap_example.dart1.3 KB
  • assets/retry_policy.dart1.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.