Skip to content

速查 06 · Riverpod ​

按问题索引。Ctrl+F 页内搜索。 系统学习见 08 · 状态管理 Riverpod, 选型见 27 · 状态管理选型与横向对比。

本项目版本:Riverpod 2.6。

目录 ​


provider 怎么选 ​

我要…用什么
只读的计算值(不会变)Provider
简单的同步状态(计数器、开关)NotifierProvider + Notifier<T>
异步请求(拉接口、读库)AsyncNotifierProvider + AsyncNotifier<T>
一次性 Future(无交互)FutureProvider
流(WebSocket、监听)StreamProvider
需要参数(按 id 查详情)任意 provider + .family
离开页面就销毁任意 provider + .autoDispose

⚠️ StateProvider / StateNotifierProvider / ChangeNotifierProvider 是旧 API, 新代码优先用 NotifierProvider / AsyncNotifierProvider。


定义一个 provider ​

手写写法(Riverpod 2.x 推荐) ​

dart
// 同步状态
class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;                       // build 里是初始值,**不要在这里做副作用**
  void increment() => state++;
}
final counterProvider =
    NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

// 异步状态
class UserNotifier extends AsyncNotifier<User> {
  @override
  Future<User> build() => ref.read(userRepoProvider).fetch();
  Future<void> refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => ref.read(userRepoProvider).fetch());
  }
}
final userProvider =
    AsyncNotifierProvider<UserNotifier, User>(UserNotifier.new);

代码生成写法(riverpod_generator) ​

dart
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}
// 跑 build_runner 后得到 counterProvider

💡 两种写法可以混用。新项目推荐 codegen(少写泛型),老项目改造用手写更省事。


watch / read / listen 怎么选 ​

用法行为用在哪
ref.watch(p)值变了重建 Widgetbuild 方法里
ref.read(p)只取一次,不订阅回调、onPressed、initState
ref.listen(p, (prev, next) {})变化时不重建,只触发副作用(弹窗、跳转)build 里注册
dart
// ❌ 别在回调里用 watch(会订阅但永远不重建,且可能报错)
onPressed: () => ref.watch(counterProvider).increment()

// ✅ 回调里用 read
onPressed: () => ref.read(counterProvider.notifier).increment()

// ✅ 监听错误弹提示
ref.listen(userProvider, (prev, next) {
  if (next.hasError) showSnackBar(next.error.toString());
});

AsyncValue 三态处理 ​

dart
final userAsync = ref.watch(userProvider);

// ① 全量处理(最常用)
userAsync.when(
  data:    (user) => Text(user.name),
  loading: () => const CircularProgressIndicator(),
  error:   (e, st) => Text('出错了:$e'),
);

// ② 只处理部分
userAsync.maybeWhen(
  data: (user) => Text(user.name),
  orElse: () => const SizedBox.shrink(),
);

// ③ 拿原始值(可能为 null,自己判空)
final user = userAsync.value;          // data 时才非 null
if (userAsync.isLoading) { /* ... */ }
if (userAsync.hasError)  { /* ... */ }

💡 想"刷新时保留旧数据":state = const AsyncLoading() 之前先存旧值, 或用 AsyncValue.guard + valueOrNull 展示旧数据 + loading 遮罩。


传参:family ​

dart
final userDetailProvider =
    AsyncNotifierProviderFamily<UserDetailNotifier, User, String>(
  UserDetailNotifier.new,
);
// 使用
ref.watch(userDetailProvider(userId));

// codegen 写法:参数直接写在 build 上
@riverpod
Future<User> userDetail(UserDetailRef ref, String id) => ...;

⚠️ family 的参数必须可被 == 比较(别传 List/Map/未重写的对象,否则每次都重建缓存)。


自动销毁:autoDispose ​

dart
final searchProvider =
    NotifierProvider.autoDispose<SearchNotifier, List<Item>>(SearchNotifier.new);

// 想"没人听也再活一会儿"
final p = SomeProvider(...);
ref.keepAlive();     // 在 build 里调用,阻止自动销毁

用途:搜索页、详情页这类离开即丢的状态,避免内存堆积。


局部刷新:select ​

dart
// ❌ 整个 user 变了就重建,哪怕只用 name
final user = ref.watch(userProvider).value;

// ✅ 只有 name 变了才重建(Riverpod 2.x 推荐)
final name = ref.watch(userProvider.select((s) => s.valueOrNull?.name));

🎯 这是性能优化第一手段:大对象只取用到的字段。


刷新与失效 ​

dart
ref.invalidate(userProvider);        // 标记失效,下次读时重新 build
ref.refresh(userProvider);           // 立即重建并返回新值
ref.invalidateSelf();                // 在 Notifier 内部让自己失效

在 Widget 中使用 ​

dart
// ① 整个 Widget 需要 provider → ConsumerWidget
class UserPage extends ConsumerWidget {
  const UserPage({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(userProvider);
    return ...;
  }
}

// ② 需要生命周期(initState / dispose)→ ConsumerStatefulWidget
class _UserPageState extends ConsumerState<UserPage> { ... }

// ③ 只有一小块需要 → Consumer 包局部(重建范围最小)
Consumer(builder: (context, ref, child) => Text(ref.watch(nameProvider)))

✅ 优先用 ③ Consumer:重建范围越小,性能越好。


测试与覆盖 ​

dart
testWidgets('显示用户名', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        userProvider.overrideWith(FakeUserNotifier.new),   // 换成假实现
      ],
      child: const MaterialApp(home: UserPage()),
    ),
  );
  expect(find.text('张三'), findsOneWidget);
});

⚠️ 忘了 ProviderScope 是最常见的新手错误(见下方报错表)。


常见报错速查 ​

报错 / 现象原因解法
Bad state: No ProviderScope found根 Widget 没包 ProviderScoperunApp(const ProviderScope(child: MyApp()))
在 initState 里 ref.watch 报错或无效initState 不能订阅改用 ref.read;或放到 build 里 watch
Cannot use "ref" after the widget was disposed异步 gap 后用了 refawait 后先判 if (!context.mounted) return;,或 if (!ref.context.mounted)
Provider was disposed / 取到旧值用了 autoDispose 且已无监听需要保留就去掉 .autoDispose 或 ref.keepAlive()
family 参数每次都重新请求传了 List/Map 或未重写 ==传基础类型或重写 ==(或配合 Freezed)
改了值但 UI 不刷新用了 ref.read 而非 watch;或没改 state 而是改了内部可变对象用 state = 新对象,别在原地 mutate
AsyncValue 一直 loadingbuild() 里的 Future 没返回 / 抛异常被吞确认 build 有 return,异常用 AsyncValue.guard

相关文档 ​