状态管理 Riverpod
状态管理、 Riverpod|Cubit
Section titled “状态管理、 Riverpod|Cubit”- Cubit 是一个轻量级状态管理,后面在补充
1. Riverpod 在干什么
Section titled “1. Riverpod 在干什么”- 本质:用「可观察的状态」驱动 UI;依赖它的 widget 在状态变化时会按需重建,效果上类似「不用手写
setState的联动刷新」,但模型是声明式(watch谁、谁才跟着变)。 - 和
setState的差别:不是「改一个变量就整页setState」,而是谁watch了这份状态,谁才 rebuild(再配合select等可以更细)。
2. autoDispose Provider
Section titled “2. autoDispose Provider”- 生命周期:当没有任何监听者(没有
watch/listen等保持订阅)时,Provider 会被自动 dispose;有新的监听时再创建。 - 和你说的「跟页面一起走」:在常见路由场景里,页面关了、树里没人再
watch,往往就会 dispose,所以体感上像跟页面生命周期一致;但严格说是**「无人订阅就销毁」**,不是 Flutter 的dispose事件本身。 - 用途:适合页面级、临时、可重建的数据(表单草稿、单次请求的缓存等),减少长期占内存。
3. 非 autoDispose(keepAlive)
Section titled “3. 非 autoDispose(keepAlive)”- 生命周期:只要
ProviderScope还在,会始终在内存中,不会因为没人watch就自动清掉。 - 刷新方式:状态变了,
watch它的 widget 会更新;若要丢弃缓存 / 强制重跑,常用invalidate/refresh - 重点(清理):需要你在合适时机主动处理(例如登出时
invalidate一批、或ref.onDispose里释放资源);不是「永不监听」,而是**「实例不自动从容器里移除」**。
重点:
- 非
autoDispose会常驻内存:适合全局会话、主题、用户信息等;若把大列表、仅某页用的数据也做成长期 Provider,容易造成内存与逻辑耦合(页面以为「走了就干净」,实际还在)。 - 实践建议:默认倾向
autoDispose+ 需要跨页长期保留时再keepAlive,比一上来全局长存更可控。
| 类型 | 典型数据 | 更推荐 |
|---|---|---|
| FutureProvider | 一次接口、详情页加载 | autoDispose:离开页面无人订阅就清缓存,避免旧数据误用;要跨页保留再 keepAlive |
| StreamProvider | 实时推送、WebSocket | 看需求:仅当前页用 → autoDispose;全局连接 → 非 dispose + 在 ref.onDispose 里关流 |
| NotifierProvider / StateNotifierProvider | 表单、列表页状态、筛选 | autoDispose 居多;全局购物车等 → 常驻 |
| AsyncNotifierProvider | 分页、提交后刷新列表 | autoDispose 很常见;登录态、全局配置 → 常驻 + 登出时 invalidate |
一句口诀
- 「只服务这一屏 / 这一流程」 → 优先
autoDispose。 - 「多屏共享、应用级」 → 常驻,并在登出 / 切换账号等节点集中
invalidate或重置。
有具体页面(例如某个 *_providers.dart)也可以贴片段,我可以按场景帮你标该用哪种
4. 常见方法
Section titled “4. 常见方法”注意:一旦 Provider 被赋值并存储在内存中(且没有被销毁),后续的 ref.watch 只是读取缓存的值,不会再次触发赋值逻辑,除非你调用了 ref.invalidate(provider) 之后销毁旧状态,下次有人读取会从头初始化
下面方法都会触发初始化
read:一次性拿值/触发动作,不订阅watch:拿值并订阅,变了会重建listen:不用于渲染,专门监听变化做副作用
-
ref.read(provider)- 适合:按钮点击、提交、调用 notifier 方法
- 特点:读取当下值,不跟随后续变化
- 常见写法:
ref.read(cartProvider.notifier).addItem()
-
ref.watch(provider)- 适合:
build中渲染 UI 数据 - 特点:建立订阅;provider 变化时触发重建
- 常见写法:
final cart = ref.watch(cartProvider)
- 适合:
-
ref.listen(provider, (prev, next) {...})- 适合:Toast、弹窗、导航、错误提示、日志
- 特点:监听变化执行回调,不靠它重建 UI
- 常见写法:监听
AsyncValue的失败后弹错误提示
-
如果你发现
watch导致重建范围太大,可以结合select使用final name = ref.watch(userProvider.select((u) => u.name));
推荐分工:
- 渲染界面 →
watch - 用户操作触发命令 →
read - 状态变化后的副作用 →
listen
易错点:
- 在
build里用read渲染数据:不会自动刷新 - 用
watch做导航/弹 Toast:可能重复触发副作用 - 把所有逻辑都塞
listen:UI 数据流会变乱
一、Provider (同步)不可变
Section titled “一、Provider (同步)不可变”不可变的特点:可覆盖,可注入
final cityProvider = Provider((ref) => 'London');final countryProvider = Provider((ref) => 'England');
// 可注入final locationProvider = Provider<String>((ref) { final city = ref.watch(cityProvider); final country = ref.watch(countryProvider); return '$city, $country';});
// 可覆盖runApp( ProviderScope( overrides: [ baseUrlProvider.overrideWithValue('https://prod-api.xxx.com'), ], child: const MyApp(), ),);
// 跨模块共享final baseUrl = ref.watch(baseUrlProvider);二、StateProvider (同步)可变的
Section titled “二、StateProvider (同步)可变的”//定义import 'package:flutter_riverpod/flutter_riverpod.dart';// 定义一个简单的数字状态,默认值为 0final counterProvider = StateProvider<int>((ref) => 0);
// 使用class CounterWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // 1. 监听状态变化(UI 会随之刷新) final count = ref.watch(counterProvider);
return Scaffold( body: Center( child: Text('当前数值: $count'), ), floatingActionButton: FloatingActionButton( // 2. 修改状态 onPressed: () { // 方式 A:直接赋值 ref.read(counterProvider.notifier).state++;
// 🚀方式 B:基于当前值进行逻辑处理(推荐,更安全),但是如果这样的话就可以使用 Notifier // ref.read(counterProvider.notifier).update((state) => state + 1); }, child: Icon(Icons.add), ), ); }}三、FutureProvider (异步)拉一次无参
Section titled “三、FutureProvider (异步)拉一次无参”适合“只读异步数据获取”(拉一次、展示结果)
// 非通知,不接收参数的,注解版本@riverpodFuture<User> user(Ref ref) async { final response = await http.get('https://api.example.com/user/123'); return User.fromJson(response.body);}
// 非通知,不接收参数的,普通版本final userProvider = FutureProvider<User>((ref) async { final response = await http.get( Uri.parse('https://api.example.com/user/123'), ); final map = jsonDecode(response.body) as Map<String, dynamic>; return User.fromJson(map);});四、FutureProvider.family (异步)拉一次有参
Section titled “四、FutureProvider.family (异步)拉一次有参”简单来说:它允许你向 Provider 传递参数来获取异步数据
-
自动处理加载中、错误和数据状态。
-
family:给这个 Provider 开了个“后门”,让它能接收一个外部参数(比如 ID、URL、索引等)。
-
自动缓存:如果你多次调用
productDetailProvider(123),Riverpod 只会发起一次网络请求,结果会被缓存。 -
自动释放:当所有订阅了 ID 为
123的组件都销毁时,这个特定参数的状态会被自动清理。 -
并发支持:你可以同时监听
id: 1和id: 2,它们是互不干扰的独立状态。// 监听 ID 为 1 的状态final product1 = ref.watch(productProvider(1));// 监听 ID 为 2 的状态final product2 = ref.watch(productProvider(2));
基本使用
可以,给你一段最小可用示例:参数变化自动重新拉取(不需要手动再调接口)。
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
*// 1) 模拟当前站点参数(你切站点时改它)*final currentCompanyIdProvider = StateProvider<int>((ref) => 1);*// 2) 带参数的 FutureProvider(family)*final productsProvider = FutureProvider.family<List<String>, int>(( ref, companyId,) async { *// 这里放真实接口:GET /products?company_id=companyId* await Future<void>.delayed(const Duration(milliseconds: 400)); return List.generate(3, (i) => 'company=$companyId product_$i')});
class DemoPage extends ConsumerWidget { const DemoPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { *// 3) watch 参数* final companyId = ref.watch(currentCompanyIdProvider); *// 4) watch 带参 povider* final productsAsync = ref.watch(productsProvider(companyId)); return Column( children: [ Row( children: [ ElevatedButton( onPressed: () => ref.read(currentCompanyIdProvider.notifier).state = 1, child: const Text('切到站点1'), ), const SizedBox(width: 8), ElevatedButton( onPressed: () => ref.read(currentCompanyIdProvider.notifier).state = 2, child: const Text('切到站点2'), ), const SizedBox(width: 8), ElevatedButton( onPressed: () { *// 同参数强制刷新才需要手动 refresh* ref.refresh(productsProvider(companyId)); }, child: const Text('手动刷新'), ), ], ), const SizedBox(height: 12), productsAsync.when( loading: () => const CircularProgressIndicator(), error: (e, _) => Text('error: $e'), data: (list) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: list.map(Text.new).toList(), ), ), ] ); }}你可以重点看这句:
ref.watch(productsProvider(companyId))
当 companyId 从 1 变 2,Riverpod 会自动用新参数重建并重新请求。
注意事项
- 参数限制:传给
family的参数必须支持==比较和 hashCode。通常使用int,String,bool或者通过freezed生成的实体类。 - 不要在内部创建参数:不要传一个每次都会刷新的对象(如新的 List),否则会导致 Provider 频繁重新计算。
// 定义一个 family,参数是一个 Listfinal myProvider = FutureProvider.family<String, List<String>>((ref, categories) async { return "Results for ${categories.join(', ')}";});
// --- 在 UI 中错误地使用 ---Widget build(context, ref) { // 错误:这里每次 build 都会创建一个 [NEW] List 实例 // 在 Dart 中,['a'] == ['a'] 结果是 false (因为内存地址不同) final asyncValue = ref.watch(myProvider(['electronics', 'books']));
return asyncValue.when( data: (data) => Text(data), loading: () => CircularProgressIndicator(), // 页面会一直停在这里不断闪烁/转圈 error: (e, _) => Text('Error'), );}五、Notifier (同步逻辑)可变的
Section titled “五、Notifier (同步逻辑)可变的”// 1. 定义 Notifier 类class Counter extends Notifier<int> { @override int build() => 0;
void increment() => state++;}
// 2. 定义 Providerfinal counterProvider = NotifierProvider<Counter, int>(Counter.new);
// ui种使用class CounterView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // 监听状态 final count = ref.watch(counterProvider);
return Scaffold( body: Center(child: Text('$count')), floatingActionButton: FloatingActionButton( // 调用封装好的 increment 方法,而不是直接修改 state onPressed: () => ref.read(counterProvider.notifier).increment(), child: Icon(Icons.add), ), ); }}六、AsyncNotifier(异步逻辑) 拉多次有参可修改
Section titled “六、AsyncNotifier(异步逻辑) 拉多次有参可修改”-
特点:
- 内部有一个
build()方法(相当于FutureProvider的函数体)。 - 可以定义**方法(Method)**来修改状态。
- 内部有一个
-
优势:可以轻松实现“乐观更新”(在网络请求回来前就先改掉 UI)。
-
if (async.isLoading / hasError / hasValue)手动判断 -
async.valueOrNull(只关心数据,不关心错误) -
async.whenData(...)(只处理 data 分支) -
ref.watch(provider.select(...))(只监听某个字段) -
监听副作用:
ref.listen(provider, ...)(弹 toast、跳转等) -
不订阅只读一次:
ref.read(provider)(事件里读取)
6.1、调用时机(敲黑板!)
Section titled “6.1、调用时机(敲黑板!)”它的调用时机非常智能,主要有以下三个时刻:
- 首次监听(最常见):
当你在 UI 中第一次执行
ref.watch(myProvider)时,build立即执行。此时状态会自动变为AsyncLoading。 - 手动刷新(Invalidate):
当你调用
ref.invalidate(myProvider)时,旧状态被销毁,build会重新运行。这常用于“下拉刷新”。 - 依赖更新:
如果你在
build内部使用了ref.watch(另一个Provider),那么当那个 Provider 变化时,build会重新运行。
6.2、build方法
Section titled “6.2、build方法”- 自动封装:当你
return一个数据(比如Product对象)时,Riverpod 会自动把它包装成AsyncData(product)放入state。 - 异步等待:因为
build是异步的,在它执行return之前,Riverpod 会自动先把state设为AsyncLoading()。 - 异常捕获:如果
build内部报错了,它会自动捕获错误并将state设为AsyncError(error)。
// 通知,不接收参数的,普通版本final randomJokeProvider = AsyncNotifierProvider<JokeNotifier, Joke>(JokeNotifier.new);class JokeNotifier extends AsyncNotifier<Joke> {
FutureOr<PaginatedProductsState> build() async { final result = await fetchProductsPageService( page: 1, pageSize: _pageSize, shopCateGoryId: _selectedShopCategoryId, sort: _sort, orderBy: _orderBy, ); return result; }
Future<void> loadJoke() async { // 1. 手动设为加载状态 (UI 会自动变转圈) state = const AsyncLoading(); try { await api.save(newName); // 2. 成功后,手动设为数据状态 (UI 自动显示新名字) state = AsyncData(newName); } catch (e, stack) { // 3. 失败后,手动设为错误状态 (UI 自动显示错误信息) state = AsyncError(e, stack); } }
void refresh() { ref.invalidateSelf(); // 这会导致 build() 重新执行 }
}
// 使用@overrideWidget build(BuildContext context, WidgetRef ref) { // 获取整个“盒子” (AsyncValue) final AsyncValue<String> asyncName = ref.watch(nameProvider);
return asyncName.when( // 情况 A:数据准备好了 (AsyncData) data: (name) => Text('你好, $name'),
// 情况 B:正在加载中 (AsyncLoading) loading: () => const CircularProgressIndicator(),
// 情况 C:出错了 (AsyncError) error: (err, stack) => Text('加载失败: $err'), );}七、状态销毁时机
Section titled “七、状态销毁时机”没有监听者,销毁的完整过程
想象你有一个 商品详情页:
- 用户进入详情页:
- 页面组件执行
ref.watch(productProvider)。 - 此时,监听者数量 = 1。
- Provider 开始工作,执行
build(),数据存入内存。
- 页面组件执行
- 用户在页面内操作:
- 你点击收藏,修改了
state。此时内存里的数据是“已收藏”。
- 你点击收藏,修改了
- 用户点击“返回”按钮:
- 详情页 Widget 被销毁(Pop)。
- 因为页面没了,
ref.watch自然也消失了。 - 此时,监听者数量 = 0。
手动使其失效(Invalidate)
这是由你或代码逻辑主动触发的:
ref.invalidate(provider):在 UI 或其他地方调用,强制销毁当前状态并重新执行build。ref.invalidateSelf():在 Notifier 内部调用,通常用于处理“加载失败后重试”或“下拉刷新”。
参数变化(仅限 .family)
如果你使用的是 AsyncNotifier.family:
- 现象:如果你从
watch(provider(1))切换到了watch(provider(2))。 - 后果:对于参数
1的状态,如果没有其他地方引用,它会根据销毁策略消失;而参数2会启动它自己的独立状态。
autoDispose
Section titled “autoDispose”autoDispose 是 Riverpod 中的一个修饰符,主要作用是在 Provider 不再被任何 Widget 或其他 Provider 监听时,自动销毁其状态并释放资源。
// 定义一个自动销毁的 Providerfinal myProvider = StateProvider.autoDispose<int>((ref) => 0);
// 使用时与普通 Provider 无异final value = ref.watch(myProvider);八,响应处理api
Section titled “八,响应处理api”九、Riverpod 异步请求工具清单(请求前 / 请求中 / 请求后)
Section titled “九、Riverpod 异步请求工具清单(请求前 / 请求中 / 请求后)”本文档面向本项目依赖的 Riverpod 3(flutter_riverpod: ^3.0.0),按「请求前 → 请求中 → 请求后」组织,并为每项补充作用说明与可独立运行的完整示例。
版本提示(Riverpod 3)
AsyncValue.valueOrNull已移除:请使用value(在 loading/error 时可能返回上一次成功值,详见官方文档对value的说明)。AsyncValue.guard的签名为guard(() async { ... }),参数是Future<T> Function(),不是直接传入Future变量。
1、状态声明(请求结果的「形状」)
Section titled “1、状态声明(请求结果的「形状」)”AsyncValue<T>
Section titled “AsyncValue<T>”作用:描述异步结果的联合类型容器;在某一时刻可能同时携带 loading、上一版 data、error 等信息(多态组合),UI 或逻辑通过 when / map / pattern matching 安全分流。
AsyncData<T> / AsyncError<T> / AsyncLoading<T>
Section titled “AsyncData<T> / AsyncError<T> / AsyncLoading<T>”作用:三种具体构造形态,用于手动写入 state(常见于 AsyncNotifier),或与 map 家族配合拿到完整状态对象(含附带字段)。
完整示例(单文件可运行):声明状态并手动构造三种 AsyncValue。
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final manualStateProvider = Provider<AsyncValue<int>>((ref) { const flag = 1; if (flag == 0) { return const AsyncValue.loading(); } if (flag == 1) { return const AsyncValue.data(42); } return AsyncValue.error(Exception('boom'), StackTrace.current);});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final s = ref.watch(manualStateProvider); return MaterialApp( home: Scaffold( body: Center(child: Text(s.toString())), ), ); }, ), ), );}2、请求中:在 Notifier 内操纵状态
Section titled “2、请求中:在 Notifier 内操纵状态”AsyncValue.guard
作用:把「try/catch + 成功写 data / 失败写 error」收敛为一次 await;得到新的 AsyncValue<T>,常用于 AsyncNotifier 的 mutation 方法。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
class CounterNotifier extends AsyncNotifier<int> { @override Future<int> build() async => 0;
Future<void> bump() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await Future<void>.delayed(const Duration(milliseconds: 50)); if (state.hasValue && state.requireValue >= 3) { throw StateError('too large'); } return state.maybeWhen(data: (v) => v + 1, orElse: () => 1); }); }}
final counterProvider = AsyncNotifierProvider<CounterNotifier, int>(CounterNotifier.new);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final async = ref.watch(counterProvider); return MaterialApp( home: Scaffold( body: Center( child: async.when( data: (v) => Text('value: $v'), error: (e, _) => Text('error: $e'), loading: () => const CircularProgressIndicator(), ), ), floatingActionButton: FloatingActionButton( onPressed: () => ref.read(counterProvider.notifier).bump(), child: const Icon(Icons.add), ), ), ); }, ), ), );}ref.onDispose
作用:在 provider 即将销毁(含 autoDispose 无人监听、或 invalidate/refresh 触发重建前等场景)时执行清理,例如取消 CancelToken、关流、关控制器。
完整示例:
import 'dart:async';
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final tickProvider = StreamProvider.autoDispose<int>((ref) { final controller = StreamController<int>(); var n = 0; final timer = Timer.periodic( const Duration(seconds: 1), (_) { n += 1; if (!controller.isClosed) controller.add(n); }, ); ref.onDispose(() { timer.cancel(); unawaited(controller.close()); }); return controller.stream;});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final async = ref.watch(tickProvider); return MaterialApp( home: Scaffold( body: Center( child: async.when( data: (v) => Text('tick: $v'), error: (e, _) => Text('$e'), loading: () => const CircularProgressIndicator(), ), ), ), ); }, ), ), );}ref.keepAlive()
作用:在 autoDispose 模式下,暂时阻止「无人监听就销毁」;返回 KeepAliveLink,调用 close() 后恢复可销毁行为。适合缓存一次成功的网络结果直到你主动释放。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final cachedProvider = FutureProvider.autoDispose<String>((ref) async { final link = ref.keepAlive(); ref.onDispose(link.close); await Future<void>.delayed(const Duration(milliseconds: 200)); return 'loaded-once';});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final async = ref.watch(cachedProvider); return MaterialApp( home: Scaffold( body: Center( child: async.when( data: (s) => Text(s), error: (e, _) => Text('$e'), loading: () => const CircularProgressIndicator(), ), ), ), ); }, ), ), );}3、请求后:读取、转换、分流(AsyncValue API)
Section titled “3、请求后:读取、转换、分流(AsyncValue API)”state.when
Section titled “state.when”作用:穷尽处理 data / error / loading 三种分支,返回同一类型 T(常用于直接 return Widget)。带 skipLoadingOnReload / skipLoadingOnRefresh / skipError 控制「多状态并存」时走哪条分支(见源码文档说明)。
完整示例:见上文 example_async_value_guard.dart 中 async.when(...)。
whenData
Section titled “whenData”作用:只对 data 做映射,得到新的 AsyncValue<NewT>;loading/error 分支会保留相应形态(适合链式数据变换)。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final rootProvider = FutureProvider<int>((ref) async => 21);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final doubled = ref.watch(rootProvider).whenData((v) => v * 2); return MaterialApp( home: Scaffold( body: Center( child: doubled.when( data: (v) => Text('doubled: $v'), error: (e, _) => Text('$e'), loading: () => const CircularProgressIndicator(), ), ), ), ); }, ), ), );}hasValue / value / requireValue
Section titled “hasValue / value / requireValue”作用:
hasValue:是否已有成功值(可能与isLoading/hasError同时为 true,例如刷新中)。value:读取当前关联值;在 loading/error 时可能仍是上一版值(Riverpod 3 用其替代旧版valueOrNull的常见用法)。requireValue:强制当前必须可读出值,否则抛错(适合逻辑层已保证必有值)。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final oneProvider = FutureProvider<int>((ref) async => 7);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final async = ref.watch(oneProvider); final summary = StringBuffer() ..writeln('hasValue: ${async.hasValue}') ..writeln('value: ${async.value}') ..writeln( () { try { return 'requireValue: ${async.requireValue}'; } catch (e) { return 'requireValue threw: $e'; } }(), ); return MaterialApp( home: Scaffold( body: Center(child: Text(summary.toString())), ), ); }, ), ), );}maybeWhen
Section titled “maybeWhen”作用:只处理关心的分支,其余走 orElse(不必写满三个回调)。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final flagProvider = StateProvider<int>((ref) => 0);
final derivedProvider = Provider<AsyncValue<String>>((ref) { final flag = ref.watch(flagProvider); if (flag == 0) return const AsyncValue.loading(); if (flag == 1) return const AsyncValue.data('ok'); return AsyncValue.error('bad', StackTrace.current);});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final text = ref.watch(derivedProvider).maybeWhen( data: (s) => 'data: $s', orElse: () => 'not only-data state', ); return MaterialApp( home: Scaffold( body: Center(child: Text(text)), floatingActionButton: FloatingActionButton( onPressed: () { ref.read(flagProvider.notifier).state = (ref.read(flagProvider) + 1) % 3; }, child: const Icon(Icons.swap_horiz), ), ), ); }, ), ), );}whenOrNull
Section titled “whenOrNull”作用:与 maybeWhen 类似,但未处理的分支返回 null(适合简短链式调用)。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final sampleProvider = Provider<AsyncValue<int>>( (ref) => const AsyncValue.data(99),);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final label = ref.watch(sampleProvider).whenOrNull(data: (v) => 'n=$v') ?? 'no data branch'; return MaterialApp( home: Scaffold(body: Center(child: Text(label))), ); }, ), ), );}map / maybeMap / mapOrNull
Section titled “map / maybeMap / mapOrNull”作用:
map:回调接收AsyncData/AsyncError/AsyncLoading完整对象,适合需要子类型上额外字段时。maybeMap:未覆盖的分支走orElse。mapOrNull:未覆盖分支返回 null。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final triStateProvider = Provider<AsyncValue<int>>((ref) { return AsyncLoading<int>(progress: 0.4);});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final s = ref.watch(triStateProvider); final viaMap = s.map( data: (d) => 'data ${d.value}', error: (e) => 'err ${e.error}', loading: (l) => 'loading p=${l.progress}', ); final viaMaybe = s.maybeMap( loading: (l) => 'only-loading: ${l.progress}', orElse: () => 'other', ); final viaOrNull = s.mapOrNull( loading: (l) => 'mapOrNull loading ${l.progress}', ); return MaterialApp( home: Scaffold( body: Center( child: Text( '$viaMap\n$viaMaybe\n$viaOrNull', textAlign: TextAlign.center, ), ), ), ); }, ), ), );}unwrapPrevious
Section titled “unwrapPrevious”作用:去掉「携带上一状态」的包装,把多态视图折叠成单纯的 loading / data / error 之一;用于避免 UI 在刷新时误把「上一版 data + loading」当成纯 loading 闪烁(常配合 ref.watch(provider).unwrapPrevious())。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
class ReloadNotifier extends AsyncNotifier<int> { @override Future<int> build() async => 1;
Future<void> reload() async { state = await AsyncValue.guard(() async { await Future<void>.delayed(const Duration(milliseconds: 100)); return state.requireValue + 10; }); }}
final reloadProvider = AsyncNotifierProvider<ReloadNotifier, int>(ReloadNotifier.new);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final raw = ref.watch(reloadProvider); final flat = raw.unwrapPrevious(); return MaterialApp( home: Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('raw: $raw'), Text('unwrapPrevious: $flat'), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => ref.read(reloadProvider.notifier).reload(), child: const Icon(Icons.refresh), ), ), ); }, ), ), );}4、请求前 / 后:失效、立即重算、监听生命周期
Section titled “4、请求前 / 后:失效、立即重算、监听生命周期”ref.invalidate(provider)
Section titled “ref.invalidate(provider)”作用:标记 provider 失效;下一次被读取时再执行 build(不会强制立刻执行)。
ref.refresh(provider)
Section titled “ref.refresh(provider)”作用:等价于 invalidate + 立即 read,立刻触发重算并拿到新状态(适合下拉刷新、重试按钮)。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final timeProvider = FutureProvider<String>((ref) async { await Future<void>.delayed(const Duration(milliseconds: 150)); return DateTime.now().toIso8601String();});
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final async = ref.watch(timeProvider); return MaterialApp( home: Scaffold( body: Center( child: async.when( data: (s) => Text(s), error: (e, _) => Text('$e'), loading: () => const CircularProgressIndicator(), ), ), persistentFooterButtons: [ TextButton( onPressed: () => ref.invalidate(timeProvider), child: const Text('invalidate'), ), TextButton( onPressed: () { ref.refresh(timeProvider); }, child: const Text('refresh'), ), ], ), ); }, ), ), );}ref.onCancel
Section titled “ref.onCancel”作用:当 最后一个监听者被移除时触发(provider 可能进入 pause / autoDispose 清理链路);不保证之后一定会 dispose(可能马上又有新监听)。适合与 keepAlive 组合做高级缓存控制。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
final logProvider = StateProvider<List<String>>((ref) => []);
final listenOnceProvider = Provider.autoDispose<void>((ref) { ref.onCancel(() { final logs = [...ref.read(logProvider)]; logs.add('onCancel at ${DateTime.now()}'); ref.read(logProvider.notifier).state = logs; });});
class TogglePage extends ConsumerStatefulWidget { const TogglePage({super.key});
@override ConsumerState<TogglePage> createState() => _TogglePageState();}
class _TogglePageState extends ConsumerState<TogglePage> { bool show = true;
@override Widget build(BuildContext context) { if (show) ref.watch(listenOnceProvider); final logs = ref.watch(logProvider); return Scaffold( appBar: AppBar(title: const Text('onCancel demo')), body: ListView( children: logs.map(Text.new).toList(), ), floatingActionButton: FloatingActionButton( onPressed: () => setState(() => show = !show), child: Icon(show ? Icons.visibility : Icons.visibility_off), ), ); }}
void main() { runApp( const ProviderScope( child: MaterialApp(home: TogglePage()), ), );}5、性能:细粒度监听 select
Section titled “5、性能:细粒度监听 select”作用:ref.watch(provider.select((state) => ...)) 只在 selector 返回值按 == 变化时重建,减少无关字段变化带来的 rebuild。
完整示例:
import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';
class ProfileNotifier extends Notifier<(String name, int age)> { @override (String, int) build() => ('Ann', 20);
void rename(String name) => state = (name, state.$2);
void birthday() => state = (state.$1, state.$2 + 1);}
final profileProvider = NotifierProvider<ProfileNotifier, (String, int)>(ProfileNotifier.new);
void main() { runApp( ProviderScope( child: Consumer( builder: (context, ref, _) { final name = ref.watch( profileProvider.select((p) => p.$1), ); return MaterialApp( home: Scaffold( body: Center(child: Text('name: $name')), persistentFooterButtons: [ TextButton( onPressed: () => ref.read(profileProvider.notifier).rename('Bob'), child: const Text('rename'), ), TextButton( onPressed: () => ref.read(profileProvider.notifier).birthday(), child: const Text('age++'), ), ], ), ); }, ), ), );}6、建议阅读顺序
Section titled “6、建议阅读顺序”- 先掌握
AsyncValue三种构造 与when/map的分流差异。 - 在
AsyncNotifier内用AsyncValue.guard写 mutation。 - 用
invalidate/refresh表达刷新语义;用onDispose/onCancel/keepAlive管理资源与缓存。 - 遇到刷新闪烁再查阅
unwrapPrevious与when的 skip 参数。
以上示例均为「单文件 + ProviderScope + runApp」的最小可运行骨架,可按需拷贝到 example/ 目录用 flutter run 验证。
第十四章、依赖库
Section titled “第十四章、依赖库”- 核心业务库(必须生成)
这些库构成了你项目的底层逻辑和数据模型:
-
collection
是一个便利列表的超集,当使用dart map的时候它不能像js map 那样返回index,可以使用collection中的
mapIndexed((index, val),当然还有更多的方法 -
riverpod_generator- 用途:将你带有
@riverpod注解的函数或类,转化为 UI 可用的fetchUserProvider或counterProvider。 - 不运行后果:你写的
extends _$Counter会持续报错,且 UI 无法找到对应的 Provider。
- 用途:将你带有
-
freezed- 用途:生成不可变对象。它会自动帮你写好
copyWith、==和hashCode(这是实现你最开始要求的 Set 集合存放对象自动去重 的核心逻辑)。 - 不运行后果:无法使用复杂的模型类。
- 用途:生成不可变对象。它会自动帮你写好
-
json_serializable- 用途:生成
fromJSON和toJSON的具体实现代码。 - 不运行后果:无法自动将后端返回的 Map 数据转换为 Dart 对象。
- 用途:生成
-
资源与路由优化(推荐生成)
这些库虽然可选,但在 2026 年的规范项目中通常都会使用:
flutter_gen_runner- 用途:将你的图片(Assets)和字体自动转化为代码变量。
- 效果:你可以写
Assets.images.logo.path而不是手动打字符串"assets/logo.png"。
go_router_builder- 用途:为 GoRouter 提供类型安全的路由跳转。
- 效果:避免手动写字符串路径,防止商城项目页面多了之后跳转出错。
总结:你的 dev_dependencies 清单
在你的 pubspec.yaml 中,凡是出现在 dev_dependencies 且名字里带有 _generator、build 或 serializable 字样的库,几乎都需要 build_runner。
2026 年的最佳操作习惯
不要每次改完都去手动运行 build。强烈建议在项目开始开发时,直接在终端开启“监视模式”:
bash
# 2026 推荐指令dart run build_runner watch --delete-conflicting-outputs请谨慎使用此类代码。
为什么一定要开 watch?
因为在 2026 年,由于 SDK 的优化,watch 模式非常轻量。它会像监听器一样守在后台,只要你保存(Ctrl+S)代码,它就会在 0.5 秒内 帮你补全生成的类,让你在编写业务逻辑时完全感觉不到代码生成的延迟