Code Generation Overview

Riverpod's code generation feature uses annotations and build_runner to automatically create providers with better type safety, performance, and developer experience.

Why Code Generation?

Code generation offers several advantages over manual provider definitions:

Automatic Disposal

Providers are automatically disposed when no longer needed, preventing memory leaks.

Type Safety

Generated providers have perfect type inference and catch errors at compile time.

Better Performance

Optimized provider implementations with minimal runtime overhead.

Less Boilerplate

Write less code while getting more features like disposal, caching, and debugging.

How It Works

The code generation process involves several steps that happen automatically when you run build_runner:

1

Annotation Parsing

build_runner scans your code for @riverpod annotations and analyzes the function signatures.

2

Code Generation

The generator creates provider classes, handles disposal logic, and generates type-safe accessors.

3

File Output

Generated code is written to *.g.dart files that are imported via part directives.

Before and After

See the difference between manual and generated providers:

Manual Provider (Old Way)

counter_manual.dart
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
  return Counter();
});

class Counter extends StateNotifier<int> {
  Counter() : super(0);

  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

// Usage: ref.watch(counterProvider);
// Access notifier: ref.read(counterProvider.notifier);

Generated Provider (New Way)

counter.dart
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

// Generated automatically:
// - counterProvider (the provider itself)
// - Auto-disposal when unused
// - Type-safe access patterns
// - Debug information and inspector support

Generated Files Structure

Understanding what gets generated helps debug issues and optimize your providers:

counter.dart
// counter.dart
part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  // Your implementation
}

// counter.g.dart (Generated)
part of 'counter.dart';

final counterProvider = NotifierProvider<Counter, int>(Counter._);

class _$Counter extends Notifier<int> {
  // Generated implementation with disposal, caching, etc.
}

// Plus debugging extensions and type definitions

Development Workflow

The typical development workflow with code generation:

  1. 1Write your provider with @riverpod annotation
  2. 2Add the part directive
  3. 3Run dart run build_runner watch (runs continuously)
  4. 4Use the generated provider in your widgets
  5. 5Iterate and let build_runner handle regeneration
Development Workflow Demo
Watch the complete development cycle from annotation to usage

Best Practices

Do

  • Use descriptive names for your providers
  • Keep build_runner watch running during development
  • Commit generated files to version control
  • Use family providers for parameterized data

Don't

  • Manually edit generated .g.dart files
  • Forget to add part directives
  • Run build_runner with --delete-conflicting-outputs unnecessarily
  • Create providers inside widget build methods

Troubleshooting

Common issues and solutions:

Build runner isn't generating files

Check that:

  • You have riverpod_generator in dev_dependencies
  • The part directive is correct
  • No syntax errors in your provider code
  • Run flutter clean and try again
Part of errors in IDE

Usually means the generated file doesn't exist yet. Run dart run build_runner build to generate the missing files.

Generated code conflicts

Use dart run build_runner build --delete-conflicting-outputs to resolve conflicts, but be careful as this deletes existing generated files.