深色模式
Riverpod 常用 API 说明
概述
Riverpod 可以理解成一组围绕 provider 和 ref 设计的 API。
provider负责声明状态或计算逻辑ref负责读取、监听、刷新和管理生命周期
如果从 API 角度看,Riverpod 最常用的内容基本就是这几类:
Provider家族ref.watch、ref.read、ref.listenselectref.invalidate、ref.refresh- 自动销毁、
keepAlive、ref.onDispose @riverpod与ProviderContainer
安装
Flutter 项目最常用的是 flutter_riverpod:
sh
flutter pub add flutter_riverpod如果准备使用代码生成,再补这些包:
sh
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner如果你还想启用 lint 规则,可以再安装:
sh
flutter pub add dev:riverpod_lintProviderScope
在 Flutter 里,Riverpod 通常从 ProviderScope 开始。它负责给整棵 Widget 树提供状态容器。
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}没有 ProviderScope,后面的 provider 和 ref 都没有运行环境。
Provider 家族
这一组 API 用来声明状态。最常见的是 Provider、FutureProvider、StreamProvider、NotifierProvider、AsyncNotifierProvider。
Provider
Provider 用来声明同步只读值。常见场景有配置、仓库对象、派生结果。
dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
final apiBaseUrlProvider = Provider<String>((ref) {
return 'https://api.example.com';
});如果一个 Provider 内部依赖其他状态,直接在里面继续用 ref.watch(...) 即可:
dart
final localeProvider = Provider<String>((ref) {
return 'zh-CN';
});
final titleMapProvider = Provider<Map<String, String>>((ref) {
return {
'zh-CN': 'Riverpod 使用说明',
'en-US': 'Riverpod Guide',
};
});
final pageTitleProvider = Provider<String>((ref) {
final locale = ref.watch(localeProvider);
final titleMap = ref.watch(titleMapProvider);
return titleMap[locale] ?? 'Riverpod';
});这里的重点不是“组合状态”这个概念本身,而是 Provider 本来就支持在内部通过 ref.watch 依赖别的 provider。
FutureProvider
FutureProvider 用来声明异步只读值,返回的是 AsyncValue<T>。
dart
final userProvider = FutureProvider<User>((ref) async {
final repository = ref.watch(userRepositoryProvider);
return repository.fetchCurrentUser();
});在 Widget 中读取时,一般配合 when:
dart
class UserPage extends ConsumerWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('加载失败:$error'),
);
}
}如果只是“发一个请求并展示结果”,通常先从 FutureProvider 开始就够了。
StreamProvider
StreamProvider 用来声明基于 Stream 的持续更新数据。
dart
final tickerProvider = StreamProvider<int>((ref) async* {
yield 1;
yield 2;
yield 3;
});它同样会返回 AsyncValue<T>,只是数据来源从一次性的 Future 变成持续推送的 Stream。
NotifierProvider
NotifierProvider 用来声明同步可修改状态。状态保存在 Notifier 的 state 里,对外通过方法修改。
dart
class CounterNotifier extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state = state + 1;
}
}
final counterProvider = NotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);读取状态和调用方法通常分开写:
dart
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();如果你需要“暴露方法并修改同步状态”,优先考虑 NotifierProvider。
AsyncNotifierProvider
AsyncNotifierProvider 用来声明异步可修改状态。它适合既要处理加载态,又要暴露方法修改状态的场景。
dart
class TodoListNotifier extends AsyncNotifier<List<Todo>> {
@override
Future<List<Todo>> build() async {
return ref.watch(todoRepositoryProvider).fetchTodos();
}
Future<void> reload() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
return ref.read(todoRepositoryProvider).fetchTodos();
});
}
}
final todoListProvider =
AsyncNotifierProvider<TodoListNotifier, List<Todo>>(
TodoListNotifier.new,
);如果异步状态除了展示结果,还需要刷新、提交、删除、重试这类方法,AsyncNotifierProvider 会比 FutureProvider 更顺手。
ref.watch、ref.read、ref.listen
这一组 API 都挂在 ref 上,但职责不同。
ref.watch
ref.watch 用来读取并订阅状态。只要依赖值变化,当前 Widget 或当前 Provider 就会重新计算。
dart
final count = ref.watch(counterProvider);它是最常用的默认读取方式。无论你是在 Widget 中读状态,还是在另一个 Provider 中依赖它,优先都先想 watch。
ref.read
ref.read 只读取当前值,不建立订阅关系。
dart
onPressed: () {
ref.read(counterProvider.notifier).increment();
}它最常见的用途有两个:
- 在事件回调里调用方法
- 在不需要响应后续变化时读取一次值
不要把 ref.read 当成“优化版 watch”去替代订阅逻辑,尤其不要为了少重建就在 build 里到处改成 read。
ref.listen
ref.listen 用来监听状态变化并执行副作用,不直接参与 UI 渲染。
dart
ref.listen<AsyncValue<User>>(userProvider, (previous, next) {
next.whenOrNull(
error: (error, stack) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('请求失败:$error')),
);
},
);
});它适合处理这类逻辑:
- 弹出
SnackBar - 页面跳转
- 打日志
- 在状态变化时执行一次性动作
一个简单的区分方式是:
- 要渲染 UI,用
ref.watch - 要点按钮调方法,用
ref.read - 要响应变化做副作用,用
ref.listen
select
select 用来从一个较大的对象中只订阅你真正关心的那部分值。
dart
final userName = ref.watch(
userProvider.select((user) => user.name),
);这里订阅的不是整个 user,而是 user.name。如果 user.age 变了但 user.name 没变,当前依赖就不会因为这个变化重新更新。
select 常见于这些位置:
- 只关心对象里的一个字段
- 列表项只订阅自己的局部数据
- 某些节点对重建频率更敏感
如果你的标题想表达“减少不必要的重建”,在 Riverpod 里更直接的 API 名字就是 select。
ref.invalidate 与 ref.refresh
这两个 API 都和重新计算状态有关。
ref.invalidate
ref.invalidate 会让目标 provider 的当前缓存失效。下次有人读取它时,它会重新计算。
dart
ref.invalidate(userProvider);它更像“标记这个状态已经过期”。
ref.refresh
ref.refresh 会立即重建目标 provider,并返回新值。
dart
final newValue = ref.refresh(userProvider);可以把它理解成更主动的一次“重算并读取”。
这两个 API 的差别可以简单记成:
invalidate:先失效,等下次再算refresh:现在就算,并把新值返回
生命周期 API
Riverpod 里和生命周期最相关的 API,通常就是自动销毁、keepAlive 和 ref.onDispose。
自动销毁
Provider 在没有监听者之后,可以被自动回收。这样可以避免状态长期占用内存。
如果你使用代码生成,未显式开启 keepAlive 时通常会按照自动销毁来处理;如果状态本来就只在局部页面短暂存在,这种行为通常是更合适的默认选择。
keepAlive
keepAlive 用来阻止自动销毁,让 Provider 即使暂时没人监听也继续保留。
在代码生成写法里,它通常长这样:
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'example.g.dart';
@Riverpod(keepAlive: true)
String example(Ref ref) {
return 'keep alive';
}适合保留的通常是这类状态:
- 登录态
- 初始化成本很高的数据
- 页面切换时不希望重复加载的数据
如果状态只在局部页面短暂使用,默认自动销毁通常更合适。
ref.onDispose
ref.onDispose 用来注册当前 Provider 被销毁时的清理逻辑。
dart
final tickerProvider = StreamProvider<int>((ref) async* {
final controller = StreamController<int>();
final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
controller.add(timer.tick);
});
ref.onDispose(() {
timer.cancel();
controller.close();
});
yield* controller.stream;
});它最常见的用途有:
- 关闭
StreamController - 取消
Timer - 释放控制器对象
- 取消订阅或断开连接
常见触发时机包括:
- 自动销毁的 Provider 被回收
- Provider 因依赖变化而重新计算,旧实例被销毁
- 整个
ProviderContainer或ProviderScope被销毁
如果 Provider 内部创建了资源,通常就应该在同一个地方顺手补上 ref.onDispose。
代码生成 API
Riverpod 支持用注解生成 Provider。常见 API 是 @riverpod 和 @Riverpod(...)。
@riverpod
@riverpod 适合最常见的默认写法。函数式和类式都可以生成对应 Provider。
函数式写法:
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user.g.dart';
@riverpod
Future<User> user(Ref ref, {required int id}) async {
final repository = ref.watch(userRepositoryProvider);
return repository.fetchUser(id);
}生成后直接这样用:
dart
final userAsync = ref.watch(userProvider(id: 1));类式写法:
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() {
return 0;
}
void increment() {
state = state + 1;
}
}@Riverpod(...)
@Riverpod(...) 用来显式传配置,比如 keepAlive: true。
dart
@Riverpod(keepAlive: true)
String sessionToken(Ref ref) {
return 'token';
}如果你不需要这些额外配置,通常直接用小写的 @riverpod 就够了。
生成命令
sh
dart run build_runner watch -d代码生成不是必须的,但如果你的项目本来就在使用生成工具,Riverpod 的生成写法通常会更统一。
ProviderContainer
ProviderContainer 是 Riverpod 的状态容器。Flutter 中我们通常通过 ProviderScope 间接使用它,但在 Widget 树外也可以直接创建。
dart
import 'package:riverpod/riverpod.dart';
final container = ProviderContainer();
try {
final value = container.read(apiBaseUrlProvider);
print(value);
} finally {
container.dispose();
}它常见于这些场景:
- 纯 Dart 环境
- 手动创建隔离的状态容器
- 测试或临时脚本
如果你是在 Flutter 页面里写业务逻辑,通常还是优先通过 ProviderScope 和 WidgetRef 使用 Riverpod。
