Generación de código con riverpod_generator y @riverpod
Use las anotaciones de riverpod_generator para producir providers con seguridad de tipos y sin código repetitivo.
Generación de código con riverpod_generator y @riverpod es una lección gratuita de Flutter Mobile Development en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Flutter Mobile Development, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Flutter Mobile Development incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
Why Code Generation?
Before Riverpod 2.0, you picked the right provider type by hand: Provider, StateProvider, FutureProvider, StreamProvider, NotifierProvider, and so on. Choosing wrong meant rewrites.
The riverpod_generator package flips this around. You write a plain function or class and add the @riverpod annotation. The generator inspects your return type and produces the correct, fully type-safe provider for you.
- Less boilerplate — no manual provider declarations.
- Type-safe parameters — pass arguments without
.familygymnastics. - Auto-disposed by default — generated providers behave like
autoDispose.
Adding the Dependencies
Code generation needs both runtime and dev-time packages. riverpod_annotation ships the @riverpod annotation you use in source. riverpod_generator and build_runner run the build step that emits the .g.dart files.
A typical pubspec.yaml for a Flutter app looks like this.
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
build_runner: ^2.4.11
riverpod_generator: ^2.4.0
custom_lint: ^0.6.4
riverpod_lint: ^2.3.10Your First Generated Provider
The smallest generated provider is a top-level function annotated with @riverpod. The first parameter is always a Ref object; the return type decides everything.
Because this function returns a plain String synchronously, the generator emits a read-only provider exposing that value. You consume it via ref.watch(helloWorldProvider) exactly like a hand-written Provider<String>.
Note the two required pieces: the part directive and the // ignore_for_file comment is optional — but the part 'file.g.dart'; is mandatory.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'hello.g.dart';
@riverpod
String helloWorld(Ref ref) {
return 'Hello, Riverpod 2.0';
}Running the Generator
The annotation alone does nothing until build_runner generates the companion .g.dart file. Run it from the project root.
- One-off build: generates once and exits.
--delete-conflicting-outputsclears stale generated files. - Watch mode: regenerates automatically every time you save a source file — ideal during active development.
After it finishes, the helloWorldProvider symbol becomes available for import.
# Generate once
dart run build_runner build --delete-conflicting-outputs
# Or watch and rebuild on save
dart run build_runner watch --delete-conflicting-outputsReturn Type Drives the Provider
The generator reads your return type and silently picks the matching provider kind. This is the core convenience of code generation: you never name a provider type again.
- Return
T→ synchronous provider (likeProvider<T>). - Return
Future<T>→ async provider exposingAsyncValue<T>(likeFutureProvider). - Return
Stream<T>→ stream provider exposingAsyncValue<T>(likeStreamProvider).
Below, simply changing the signature to Future turns it into an async provider — no other change needed.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user.g.dart';
@riverpod
Future<String> userName(Ref ref) async {
await Future<void>.delayed(const Duration(seconds: 1));
return 'Ada Lovelace';
}Passing Parameters (No More .family)
With hand-written providers, parameterizing meant .family and a tuple-like single argument. The generator lets you add normal function parameters after ref, and they become strongly typed provider arguments.
Here messageProvider takes an int id. You call it as ref.watch(messageProvider(42)). Multiple parameters and named/optional parameters all work.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'message.g.dart';
@riverpod
Future<String> message(Ref ref, int id) async {
final repo = ref.watch(messageRepositoryProvider);
return repo.fetchById(id);
}
// Usage in a widget:
// final msg = ref.watch(messageProvider(42));Stateful Logic: The Notifier Class
For mutable state with methods, annotate a class that extends the generated base class _$ClassName. You override build() to return the initial state; the generator wires up a NotifierProvider for you.
Inside methods you mutate state, and listeners rebuild automatically. This replaces the old Notifier + manual NotifierProvider declaration with a single annotated class.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void reset() => state = 0;
}Async Notifiers
If your build() returns a Future, the generator produces an AsyncNotifier. The exposed state is an AsyncValue<T> that automatically tracks loading, data, and error states.
To update state after an async action, assign AsyncValue.guard(...) to state — it runs your async code and captures success or error without manual try/catch.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'todos.g.dart';
@riverpod
class Todos extends _$Todos {
@override
Future<List<String>> build() async {
return ref.watch(todoRepositoryProvider).fetchAll();
}
Future<void> add(String title) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await ref.read(todoRepositoryProvider).create(title);
return ref.read(todoRepositoryProvider).fetchAll();
});
}
}Consuming Generated Providers
Generated providers are consumed exactly like manual ones — the generated symbol is <name>Provider for functions, or <ClassName>Provider for Notifier classes.
ref.watch(counterProvider)→ the current state value.ref.read(counterProvider.notifier)→ the Notifier instance, to call methods likeincrement().- For async providers, watch returns an
AsyncValueyou handle with.when(...).
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Text('Add'),
),
],
);
}
}Keep-Alive and Dependencies
Generated providers are auto-disposed by default — they drop their state when no longer watched. Two annotation options give you control:
@Riverpod(keepAlive: true)— keeps the provider alive even with no listeners (use for app-wide singletons like a Dio client).@Riverpod(dependencies: [...])— declares scoped overrides for provider scoping. Most apps don't need this.
The capitalized @Riverpod(...) form is just the configurable version of the lowercase @riverpod shorthand.
import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'http.g.dart';
@Riverpod(keepAlive: true)
Dio dio(Ref ref) {
return Dio(BaseOptions(baseUrl: 'https://api.example.com'));
}Pure Dart: Why the Logic Is Testable
A big payoff of code generation is that your provider bodies are plain Dart functions and classes — easy to reason about and unit test. Below is a standalone illustration of the same state++ mutation logic a generated Notifier would run, with no Flutter or Riverpod imports needed.
This kind of pure logic is exactly what you keep inside a generated @riverpod class so it stays trivial to test.
class Counter {
int state = 0;
void increment() => state++;
void reset() => state = 0;
}
void main() {
final counter = Counter();
counter.increment();
counter.increment();
counter.increment();
print('After 3 increments: ${counter.state}');
counter.reset();
print('After reset: ${counter.state}');
}Quick Check
You annotate a function that returns Future<List<Product>> with @riverpod. What kind of provider does riverpod_generator emit, and how do you consume it in a widget?
Recap
You learned how riverpod_generator removes provider boilerplate:
- Add
riverpod_annotation(runtime) plusriverpod_generatorandbuild_runner(dev), and apart '<file>.g.dart';directive. - Annotate a function for read-only/derived values, or a class extending
_$Namefor stateful Notifiers. - The return type chooses the provider:
T→ sync,Future<T>→ async (AsyncValue),Stream<T>→ stream. - Add normal parameters after
refinstead of.family. - Run
dart run build_runner watchto regenerate on save. - Providers are auto-disposed by default; use
@Riverpod(keepAlive: true)for app-wide singletons.
Preguntas frecuentes
¿La lección «Generación de código con riverpod_generator y @riverpod» es gratis?
Sí — el texto completo de «Generación de código con riverpod_generator y @riverpod» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Flutter Mobile Development, actualiza a CoddyKit PRO. El curso de Flutter Mobile Development incluye 4 lecciones en total.
¿Qué aprenderé en «Generación de código con riverpod_generator y @riverpod»?
Use las anotaciones de riverpod_generator para producir providers con seguridad de tipos y sin código repetitivo. Practicas Flutter Mobile Development con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Flutter Mobile Development?
No se requiere experiencia previa. Flutter Mobile Development en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Generación de código con riverpod_generator y @riverpod»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Flutter Mobile Development?
Sí. Cada lección de Flutter Mobile Development incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- De Provider a Riverpod: migración del estado heredado
- Generación de código con riverpod_generator y @riverpod
- Canalizaciones de datos con AsyncNotifier y FutureProvider
- Ámbitos, reemplazos y ProviderObserver