Code Generation
Code generation is a Riverpod feature that automatically generates provider boilerplate using annotations, resulting in cleaner, safer, and more maintainable code.
What is it?
Traditionally, creating providers requires manually declaring them.
Example:
final counterProvider = NotifierProvider<Counter, int>(
Counter.new,
);
class Counter extends Notifier<int> {
@override
int build() => 0;
}
With code generation, you only write the business logic.
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
}
Riverpod generates the provider automatically.
Instead of writing repetitive boilerplate, you focus on implementing the provider's behavior.
Why does it exist?
As applications grow, manually creating providers becomes repetitive.
Without code generation, every provider requires:
- Declaring the provider variable
- Choosing the correct provider type
- Specifying generic types
- Keeping provider names in sync
- Writing additional boilerplate
Code generation solves these problems by:
- Reducing boilerplate
- Improving type safety
- Eliminating repetitive code
- Providing compile-time validation
- Supporting automatic provider naming
- Simplifying provider families
Syntax
Step 1: Add Dependencies
dependencies:
flutter_riverpod: ^2.x.x
riverpod_annotation: ^2.x.x
dev_dependencies:
riverpod_generator: ^2.x.x
build_runner: ^2.x.x
Explanation:
riverpod_annotationprovides annotations such as@riverpod.riverpod_generatorgenerates provider code.build_runnerexecutes the generator.
Step 2: Add a Part File
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
Explanation:
- The
partdirective links the generated file. - The
.g.dartfile is created automatically.
Step 3: Annotate a Provider
@riverpod
String appName(AppNameRef ref) {
return 'Riverpod';
}
Explanation:
@riverpodmarks the function for code generation.- Riverpod generates the corresponding provider.
AppNameRefis also generated.
Step 4: Generate the Code
dart run build_runner build
Explanation:
- Scans your project for annotations.
- Generates all
.g.dartfiles.
For continuous generation during development:
dart run build_runner watch
Explanation:
- Watches for file changes.
- Automatically regenerates code when needed.
Mental Model
Without code generation:
Write Provider
│
Write Boilerplate
│
Write Types
│
Maintain Everything
With code generation:
Write Logic
│
▼
@riverpod
│
▼
Generator
│
▼
Creates Provider
You write the logic.
The generator writes the boilerplate.
Generated Provider Names
Suppose you write:
@riverpod
String greeting(GreetingRef ref) {
return 'Hello';
}
Riverpod generates:
final greetingProvider = Provider<String>(...);
You use it like any manually created provider.
ref.watch(greetingProvider);
Examples
Function Provider
@riverpod
String greeting(GreetingRef ref) {
return 'Hello Riverpod';
}
Explanation:
- Generates a
Provider<String>. - No manual provider declaration is required.
Notifier
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() {
state++;
}
}
Explanation:
- Generates a
NotifierProvider. _$Counteris generated automatically.- Only business logic is written manually.
AsyncNotifier
@riverpod
class User extends _$User {
@override
Future<UserModel> build() async {
return repository.fetchUser();
}
}
Explanation:
- Generates an
AsyncNotifierProvider. - Handles asynchronous state automatically.
Provider Family
@riverpod
String userName(UserNameRef ref, int id) {
return 'User $id';
}
Explanation:
- Generates a provider family automatically.
- No need to manually use the
.familymodifier.
Generated Files
Suppose you have:
lib/providers/user_provider.dart
Running the generator creates:
lib/providers/user_provider.g.dart
The generated file contains:
- Provider declarations
- Generated reference types
- Hashes
- Internal implementation details
You should never edit the generated file manually.
When to Use
Use code generation when:
- Building medium or large applications
- Using
Notifier - Using
AsyncNotifier - Creating provider families
- Reducing boilerplate
- Improving maintainability
It is the recommended approach for modern Riverpod projects.
When NOT to Use
Manual providers may be sufficient when:
- Learning Riverpod
- Building very small applications
- Creating quick prototypes
- Avoiding build-time tooling
Even then, code generation remains a valid choice.
Best Practices
- Use
@riverpodfor new providers. - Commit generated
.g.dartfiles to version control (unless your team's workflow differs). - Never edit generated files manually.
- Run
build_runner watchduring development. - Keep provider logic inside the annotated function or class, not in the generated code.
Common Mistakes
1. Forgetting the Part File
❌ Wrong
import 'package:riverpod_annotation/riverpod_annotation.dart';
@riverpod
String greeting(GreetingRef ref) {
return 'Hello';
}
Why it's wrong:
- The generator cannot link the generated code.
✔ Correct
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'greeting_provider.g.dart';
2. Forgetting to Run the Generator
❌ Wrong
@riverpod
String greeting(GreetingRef ref) {
return 'Hello';
}
Why it's wrong:
- The generated provider does not exist yet.
- Compilation fails.
✔ Correct
Run:
dart run build_runner build
or
dart run build_runner watch
3. Editing Generated Files
❌ Wrong
// greeting_provider.g.dart
final greetingProvider = ...
// Modified manually
Why it's wrong:
- Changes are overwritten the next time code generation runs.
✔ Correct
Only edit the original annotated file.
Related APIs
- @riverpod
- Notifier
- AsyncNotifier
- StreamNotifier
- Provider Families
- build_runner
- riverpod_annotation
- riverpod_generator
Summary
Code generation allows Riverpod to automatically generate providers from annotated functions and classes. By using @riverpod, you write only the business logic while Riverpod generates the provider declarations, reference types, and boilerplate. This approach reduces repetitive code, improves type safety, and is the recommended way to build modern Riverpod applications.