使用 riverpod_generator 和 @riverpod 生成代码
使用 riverpod_generator 注解生成类型安全的提供者,避免编写样板代码。
使用 riverpod_generator 和 @riverpod 生成代码 是 CoddyKit 上的免费 Flutter Mobile Development 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Flutter Mobile Development 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Flutter Mobile Development 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
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.
常见问题解答
「使用 riverpod_generator 和 @riverpod 生成代码」课时是免费的吗?
是的 — 「使用 riverpod_generator 和 @riverpod 生成代码」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Flutter Mobile Development 课程的其余内容,请升级到 CoddyKit PRO。 Flutter Mobile Development 课程共包含 4 节课。
「使用 riverpod_generator 和 @riverpod 生成代码」这节课中我会学到什么?
使用 riverpod_generator 注解生成类型安全的提供者,避免编写样板代码。 你通过在浏览器中直接运行的动手代码来练习 Flutter Mobile Development,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Flutter Mobile Development 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Flutter Mobile Development 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 riverpod_generator 和 @riverpod 生成代码」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Flutter Mobile Development 课中编写并运行代码吗?
能。每节 Flutter Mobile Development 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 从 Provider 到 Riverpod:迁移旧状态管理
- 使用 riverpod_generator 和 @riverpod 生成代码
- AsyncNotifier 与 FutureProvider 数据流水线
- 提供者作用域、覆盖与 ProviderObserver