Code Generation with @riverpod
🎯 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
- Completion of Phase 3: Provider Taxonomy & Decision Trees.
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.
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
- 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 |
appTitleProvider |
AutoDisposeProvider<String> |
@riverpod |
userProvider |
AutoDisposeFutureProvider<User> |
@riverpod |
cartNotifierProvider |
AutoDisposeNotifierProvider<CartNotifier, CartState> |
3. Functional Providers vs Class-Based Notifiers
Ref ref._$ClassName with a build() method.Functional Provider Syntax
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
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:
@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:
# 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
🏋️ 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.