skip to content

Annotation Codegen

riverpod_generator turns @riverpod functions and classes into providers, deriving names, families from parameters and keepAlive from the annotation. Interviewers ask when hand-written ones fit.

part ofFlutter Riverpodoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Riverpod 3 with riverpod_generator, what does annotating a function versus a Notifier class with @riverpod generate, and how is it named?

level: juniorimportance: must knowfreq 55%

answer

  1. return type picks the provider kind
  2. Future or Stream becomes AsyncValue
  3. class extends _$ClassName, overrides build()
  4. public methods reached via .notifier
  5. trailing Notifier stripped, Provider appended

basics

~20 s

An annotated function with a Ref first parameter becomes a read-only functional provider whose kind follows its return type; an annotated class extending _$Name with build() becomes a notifier provider. Names are lower camel case plus Provider, with a trailing Notifier stripped.

solid answer

~40 s

riverpod_generator reads the annotated element. A function whose first parameter is a `Ref` becomes a *functional provider*: a plain return type behaves like `Provider`, a `Future`/`FutureOr` like `FutureProvider`, a `Stream` like `StreamProvider`, and async results reach readers as `AsyncValue`. A class annotated `@riverpod` that extends the generated `_$ClassName` and overrides `build()` becomes a notifier provider — sync `build` gives a `Notifier`, `Future` an `AsyncNotifier`, `Stream` a `StreamNotifier` — and its public methods are how the UI changes state through `ref.read(xProvider.notifier)`. The variable name is the element name, minus anything matching the default strip pattern `Notifier$`, lower-cased first letter, plus `Provider`: `fetchUser` gives `fetchUserProvider`, `StepCounterNotifier` gives `stepCounterProvider`. `@Riverpod(name: ...)` replaces the derived name verbatim.

code

dart · 23 lines
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'steps.g.dart';

// Generates stepGoalProvider: sync, read-only, auto-dispose.
@riverpod
int stepGoal(Ref ref) => 10000;

// Generates workoutNamesProvider; readers get AsyncValue<List<String>>.
@riverpod
Future<List<String>> workoutNames(Ref ref) async {
  await Future<void>.delayed(const Duration(milliseconds: 200));
  return ['Run', 'Row', 'Cycle'];
}

// Generates stepCounterProvider: the trailing Notifier is stripped.
@riverpod
class StepCounterNotifier extends _$StepCounterNotifier {
  @override
  int build() => 0;

  void addSteps(int steps) => state += steps;
}

go deeper

for a junior

Recall the two shapes: annotated function for a read-only value, annotated class extending _$Name with build() for state the UI changes. Know that the variable is the name plus Provider.

for a middle

Explain how the return type selects the provider kind, how async results become AsyncValue, and how the strip pattern, prefix and suffix build the name, including @Riverpod(name:).

for a senior

Show judgement on when a derived value should become a class, the Raw escape hatch and its disposal cost, and the Riverpod 3 changes such as the removed Ref subclasses.

for a principal

Frame the function-versus-class split as a team convention: who may mutate state, how naming options keep call sites readable, and how lint rules keep the contract enforced.

## What `@riverpod` is `@riverpod` is a constant instance of the `Riverpod` annotation class from `package:riverpod_annotation` (`const riverpod = Riverpod();`). It does nothing at runtime. **riverpod_generator** — version 4.0.9 alongside Riverpod 3.4.3 — reads every annotated function or class in a library that declares a `part '<file>.g.dart';` directive and writes the matching provider into that generated part. The generator accepts two shapes of source, and the shape you pick decides what the UI can do with the result. ## Functional providers: an annotated function A top-level function annotated with `@riverpod` whose **first positional parameter is a `Ref`** becomes a *functional provider*. Its body is the creation logic, and whatever it `ref.watch`es becomes a dependency. - A plain return type (`int`, `Settings`) behaves like a hand-written `Provider`. - A `Future<T>` or `FutureOr<T>` return type behaves like a `FutureProvider`; readers get an `AsyncValue<T>`. - A `Stream<T>` return type behaves like a `StreamProvider`, again exposed as `AsyncValue<T>`. - Wrapping the type in `Raw<...>` — a typedef with no runtime effect — opts out of that conversion, so readers receive the raw `Future` or `Stream`. Riverpod's documentation describes functional providers as syntax sugar for a class-based provider that has nothing but a `build` method. They therefore **cannot be changed from outside**: there is no notifier with public methods, so the value is recomputed only when something it watches changes or when it is invalidated. ## Class-based providers: an annotated Notifier class A class annotated with `@riverpod` must **extend the generated base class `_$ClassName`** and override `build()`. The generator derives the notifier kind from `build`'s return type: | `build()` returns | Generated base extends | Behaves like | |---|---|---| | `T` | `$Notifier<T>` | `NotifierProvider` | | `Future<T>` | `$AsyncNotifier<T>` | `AsyncNotifierProvider` | | `Stream<T>` | `$StreamNotifier<T>` | `StreamNotifierProvider` | The class's **public methods are its side-effect API**. A widget calls `ref.read(stepCounterProvider.notifier).addSteps(500)`, and the method assigns `state`. That is the capability a functional provider lacks. riverpod_lint enforces the contract: `notifier_extends` fires when the `extends _$ClassName` clause is missing, `notifier_build` when `build` is missing, and an assist converts a functional provider into the class variant and back. ## How the generated name is derived You never name the provider variable yourself. The generator takes the function or class name and applies its build options, in this order: 1. Remove whatever matches `provider_name_strip_pattern`, whose default is the regular expression `Notifier$` (a trailing `Notifier`). 2. Lower-case the first letter (with the default empty prefix). 3. Append the suffix, `Provider` by default (`provider_family_name_suffix` applies to providers with parameters). So `fetchUser` becomes `fetchUserProvider`, `StepCounterNotifier` becomes `stepCounterProvider`, and a class named plain `StepCounter` also becomes `stepCounterProvider`. Passing `@Riverpod(name: 'dailyGoalProvider')` uses that string as-is and skips the build.yaml transformations; it must be unique within the library. Prefix, suffix and strip pattern live under the `riverpod_generator` builder's `options` in `build.yaml`. ## What changed in Riverpod 3 - Functional providers take the single unified `Ref`. Riverpod 2-era generated code used a per-provider type such as `FetchUserRef`; Riverpod 3 removed every `Ref` subclass. - The hand-written `Notifier`/`FamilyNotifier`/`AutoDisposeNotifier` split was merged into `Notifier`; the generated `_$` classes hid that split anyway. - riverpod_generator 4.0.0 stopped emitting the provider variables as constants. ## What the generated part file contains Opening the `.g.dart` file demystifies the annotation. For each annotated element the generator writes: - a top-level `final` variable — `stepCounterProvider` — annotated with an internal `@ProviderFor(...)` marker that lets the linter map the provider back to your source; - a provider class, such as `StepCounterProvider`, constructed with the provider's `name`, `isAutoDispose`, `retry` and `dependencies` values taken from the annotation; - for a class-based provider, the abstract `_$StepCounterNotifier` base your class extends, which declares `build()` and wires it to the provider element; - a source hash function used to detect edits, which is what makes stateful hot reload of a single provider possible; - an `overrideWithValue` helper, so tests can replace the provider with a fixed value. None of this is meant to be edited: the file is regenerated whenever the source changes, and hand edits are lost. Reading it once is still the fastest way to confirm what kind of provider, lifetime and name the generator chose. ## Picking between the two shapes Start with a function when the value is derived — a computed step goal, a fetched workout list, a stream of sensor readings — and nothing outside should mutate it. Move to a class when the UI must change the state: logging a set, resetting a streak, toggling a unit preference. Both shapes support async results, parameters and the same annotation options (`keepAlive`, `dependencies`, `retry`, `name`), so the choice is about who may change the value, not about what the value is.

  • Can a functional @riverpod provider change its value later without anything it watches changing?
    Not from outside. It has no notifier with public methods, so widgets cannot mutate it; it recomputes only when a watched dependency changes or when it is invalidated. If callers need to change the state, turn it into a class with methods. riverpod_lint offers an assist that converts a functional `@riverpod` provider into the class variant and back.
  • How do you stop riverpod_generator from wrapping a Future result in AsyncValue?
    Declare the return type as `Raw<Future<T>>`. `Raw` is a typedef with no runtime effect that the generator and linter read as metadata, so `ref.watch` then returns the `Future` itself. The same wrapper lets a provider return an object such as a `ChangeNotifier`, which Riverpod then neither listens to nor disposes for you.
  • What does the generated _$ClassName base class give a @riverpod Notifier?
    It extends the matching internal base (`$Notifier<T>`, `$AsyncNotifier<T>` or `$StreamNotifier<T>`), declares `build` abstractly with your signature, and wires `runBuild` so the element calls your `build`. For a class with `build` parameters it also stores the arguments and exposes one getter per parameter. riverpod_lint's `notifier_extends` warns when the class does not extend it.

saying these in an interview costs you the question

  • The provider kind is chosen by an argument on the annotation, such as a type flag.
  • A function annotated with @riverpod generates a notifier the UI can mutate.
  • With default options, CounterNotifier generates counterNotifierProvider.
  • Riverpod 3 functional providers still need a generated FetchUserRef parameter type.
  • An async @riverpod function exposes a raw Future that every widget must await.
open as a page

In Riverpod 3, when does riverpod_generator's @riverpod syntax pay off, and when do hand-written providers fit better?

level: middleimportance: must knowfreq 50%

basics

~20 s

Riverpod's docs recommend riverpod_generator only when a project already runs code generation, such as freezed or json_serializable. It buys automatic provider kinds, any parameters, auto-dispose defaults and stateful hot reload, at the cost of a slow build step.

open as a page

With riverpod_generator, how do extra parameters on a @riverpod function or a Notifier's build method turn a provider into a family?

level: middleimportance: should knowfreq 40%

basics

~20 s

Any parameter after the Ref of an annotated function, or any build() parameter of an annotated Notifier, makes the generated provider a family whose call mirrors that parameter list, including named, optional and defaulted parameters and type parameters.

open as a page

With riverpod_generator, why is a @riverpod provider disposed once nothing listens to it, and what does @Riverpod(keepAlive: true) change?

level: middleimportance: should knowfreq 42%

basics

~10 s

riverpod_generator makes providers auto-dispose by default, because the annotation's keepAlive field defaults to false. @Riverpod(keepAlive: true) generates a provider that is not auto-disposed, matching a hand-written provider declared without autoDispose.

open as a page

Migrating a fitness app's hand-written Riverpod 3 providers to @riverpod, what behaviour changes and what breaks at the call sites?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Generated providers auto-dispose by default, so state that relied on staying alive now resets; names are derived, so call sites and overrides change; families take real parameter lists; and legacy StateNotifier or ChangeNotifier providers must be rewritten, since codegen does not generate them.

open as a page

What does Riverpod's riverpod_lint package catch in @riverpod code, and how is it enabled in current Riverpod 3 projects?

level: middleimportance: nice to knowfreq 22%

basics

~10 s

riverpod_lint adds Riverpod-specific warnings, quick fixes and assists to the Dart analyzer. Since 3.1.0 it runs on analysis_server_plugin, enabled under plugins: in analysis_options.yaml, and checks the codegen contract plus general Riverpod mistakes.

open as a page