Phase 04

Code Generation with @riverpod

Calculating reading time... Module 1: Foundations

🎯 Learning Objective

Master Riverpod 3 code generation using riverpod_generator and build_runner. Understand @riverpod annotations, generated .g.dart files, naming conventions, dynamic Ref type inference, and family parameter syntax.

📋 Prerequisites

1. Why Code Generation is Mandatory in Riverpod 3

In legacy Riverpod, writing providers manually required repetitive boilerplate, explicit generic type annotations, and verbose family classes. Code generation automates this completely.

✨ Advantages of @riverpod Code Generation

1. Zero Boilerplate: Type parameters (Ref, AutoDisposeNotifierProvider) are generated automatically.
2. Simplified Families: Dynamic parameters are added directly as function arguments.
3. Default autoDispose: All generated providers autoDispose by default to prevent memory leaks.
4. Future & Stream Support: Returning a FutureOr<T> automatically generates an AsyncNotifierProvider.

Code Diff: Legacy Manual vs Modern @riverpod

Diff Converting Legacy Provider to @riverpod Code Gen
- final userProvider = AutoDisposeAsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);
- class UserNotifier extends AutoDisposeAsyncNotifier<User> { ... }

+ part 'user_provider.g.dart';
+
+ @riverpod
+ class UserNotifier extends _$UserNotifier {
+   @override
+   Future<User> build() async => fetchUser();
+ }

2. How Code Generation Works

Code generation relies on Dart's part file mechanism. The generator reads @riverpod annotations and creates a hidden .g.dart file.

Naming Rules & Generated Provider Symbols

When you annotate a function or class with @riverpod, Riverpod automatically appends Provider to your function or class name:

Your Written Code Generated Provider Name Generated Provider Type
@riverpod
String appTitle(Ref ref)
appTitleProvider AutoDisposeProvider<String>
@riverpod
Future<User> user(Ref ref)
userProvider AutoDisposeFutureProvider<User>
@riverpod
class CartNotifier extends _$CartNotifier
cartNotifierProvider AutoDisposeNotifierProvider<CartNotifier, CartState>

3. Functional Providers vs Class-Based Notifiers

Which @riverpod syntax should I write?
Read-Only Calculation / Future
Functional Provider
Write a top-level function taking Ref ref.
State with Operational Methods
Class Notifier
Write a class extending _$ClassName with a build() method.

Functional Provider Syntax

Dart lib/features/config/config_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'config_provider.g.dart';

@riverpod
Future<AppConfig> appConfig(Ref ref) async {
  return fetchRemoteConfig();
}

Class Notifier Syntax with Family Parameters

Dart lib/features/products/product_notifier.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'product_notifier.g.dart';

@riverpod
class ProductNotifier extends _$ProductNotifier {
  @override
  Future<Product> build(String productId) async {
    // productId is automatically converted into a Family parameter!
    return ref.watch(productRepoProvider).fetchById(productId);
  }

  Future<void> updatePrice(double newPrice) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      return ref.read(productRepoProvider).updatePrice(productId, newPrice);
    });
  }
}

4. Controlling Disposal & Build Runner Commands

By default, @riverpod creates auto-disposing providers. To keep state alive permanently in memory (e.g. user authentication session), pass keepAlive: true:

Dart Persistent KeepAlive Provider
@Riverpod(keepAlive: true)
class UserSessionNotifier extends _$UserSessionNotifier {
  @override
  UserSession build() => const UserSession.anonymous();
}

Running Code Generation Terminal Commands

Execute the following commands in your terminal to trigger code generation:

Bash Terminal Commands
# 1. One-time build (Deletes conflicting outputs)
dart run build_runner build --delete-conflicting-outputs

# 2. Watch mode (Automatically re-generates .g.dart files on save)
dart run build_runner watch --delete-conflicting-outputs

5. Knowledge Check & Mini Challenge

❓ Question: What class must a generated class Notifier extend?
A) _$ClassName (Generated abstract class)
B) Notifier
C) StateNotifier

🏋️ Mini Challenge: Annotated User Notifier

Write an annotated @riverpod class Notifier named UserSettingsNotifier that inherits from _$UserSettingsNotifier and returns a boolean state in build().

Phase 04 Summary

You have learned why code generation is mandatory in Riverpod 3, how .g.dart files work, how provider names are derived, and how to write functional and class-based @riverpod annotations.