在Flutter状态管理领域,Riverpod 2.x 无疑是当前最主流、最优雅的解决方案之一。作为 Provider 的升级版,它彻底解决了 Provider 嵌套冗余、状态作用域混乱、内存泄漏等痛点,凭借“无上下文依赖、强类型安全、自动状态管理、灵活的状态组合”等特性,成为中大型 Flutter 项目的首选状态管理方案。
很多开发者从 Provider 迁移到 Riverpod 2.x 时,容易陷入“只知其然,不知其所以然”的困境——会用基本的 Provider、StateNotifierProvider,但不懂其设计思想,也不清楚不同场景下该选择哪种 Provider、如何规避常见坑。
本文将从 Riverpod 2.x 的核心设计思想出发,结合5个高频实战案例(覆盖全局状态、页面级状态、列表状态、表单状态、状态组合),拆解其最佳实践,帮你真正吃透 Riverpod 2.x,写出简洁、高效、可维护的 Flutter 代码。
一、先搞懂:Riverpod 2.x 的核心设计思想
Riverpod 2.x 的设计思想,本质是“解耦、精准、可扩展”,它的所有特性都是围绕这三个核心展开,彻底解决了 Provider 的固有缺陷。我们先理清3个核心设计理念,再看具体用法。
1. 无上下文(Context)依赖,彻底摆脱嵌套地狱
这是 Riverpod 最核心的改进,也是它与 Provider 最本质的区别。Provider 依赖 InheritedWidget,必须通过 Context 才能获取状态,这就导致了“嵌套层级过深”的问题(比如全局状态需要嵌套在顶层,页面状态需要嵌套在页面内部)。
而 Riverpod 2.x 引入了 ProviderScope(或 ProviderContainer),将状态与 Context 完全解耦:所有 Provider 都注册在全局或局部的容器中,组件无需依赖 Context,只需通过 Provider 本身就能获取状态,彻底告别嵌套冗余。
2. 强类型安全,编译期校验,减少 runtime 异常
Riverpod 2.x 基于 Dart 的泛型特性,实现了完全的类型安全。无论是定义 Provider、获取状态,还是修改状态,都能在编译期进行类型校验,避免了 Provider 中“类型转换错误”“状态为空”等常见 runtime 异常。
比如,你定义一个返回 String 类型的 Provider,就无法在组件中将其当作 int 类型使用,编译器会直接报错,提前规避潜在问题。
3. 状态分层管理,精准控制作用域与生命周期
Riverpod 2.x 不强制所有状态全局共享,而是允许开发者根据状态的作用范围,灵活定义“全局状态”“页面级状态”“局部组件状态”,并自动管理其生命周期——页面销毁时,页面级状态自动释放,无需手动处理,从根源上避免内存泄漏。
同时,Riverpod 支持“状态组合”,可以将多个独立的状态组合成一个新的状态,无需修改原有状态逻辑,灵活性大幅提升。
4. 自动缓存与重建优化,提升应用性能
Riverpod 2.x 会自动缓存 Provider 的状态,只有当状态依赖的上游状态发生变化时,才会重新计算并通知依赖该状态的组件重建。这种“按需重建”的机制,避免了 Provider 中“修改一个状态,多个无关组件强制重建”的问题,性能更优。
二、必备基础:Riverpod 2.x 核心 API 速览(必掌握)
在看案例之前,先快速掌握 Riverpod 2.x 最常用的5个核心 API,后续案例都会基于这些 API 展开,避免看不懂代码。
1. Provider:无状态的只读状态(用于共享静态数据/计算结果)
适用于不需要修改的状态,比如全局配置、常量、计算衍生值(如“购物车总价”由“商品列表”计算得出)。
// 示例:全局配置 Provider(只读)
final appConfigProvider = Provider((ref) {
return AppConfig(
baseUrl: "https://api.example.com",
timeout: Duration(seconds: 10),
);
});
// 组件中使用
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 获取只读状态,无需监听变化(也可监听)
final appConfig = ref.watch(appConfigProvider);
return Text("接口地址:${appConfig.baseUrl}");
}
}
2. StateProvider:简单的可修改状态(用于单一值的简单状态)
适用于简单的、单一值的可修改状态,比如“主题模式”“登录状态”“计数器”,底层基于 StateNotifier 实现,用法简洁。
// 示例:计数器 StateProvider
final counterProvider = StateProvider((ref) => 0);
// 组件中使用(修改+监听)
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听状态变化
final count = ref.watch(counterProvider);
return Column(
children: [
Text("计数:$count"),
ElevatedButton(
onPressed: () {
// 修改状态(两种方式)
ref.read(counterProvider.notifier).state += 1;
// 或 ref.read(counterProvider.notifier).update((state) => state + 1);
},
child: Text("增加"),
),
],
);
}
}
3. StateNotifierProvider:复杂的可修改状态(用于多逻辑、多值的状态)
适用于复杂状态管理,比如“商品列表”“用户信息”“表单数据”,支持多方法、多状态值,逻辑清晰,可维护性强,是 Riverpod 2.x 中最常用的 API。
// 示例:用户信息 StateNotifierProvider
// 1. 定义状态模型
class UserState {
final String name;
final int age;
final bool isLogin;
UserState({required this.name, required this.age, required this.isLogin});
// 拷贝状态(不可变对象,修改时需返回新对象)
UserState copyWith({String? name, int? age, bool? isLogin}) {
return UserState(
name: name ?? this.name,
age: age ?? this.age,
isLogin: isLogin ?? this.isLogin,
);
}
}
// 2. 定义 StateNotifier(处理状态逻辑)
class UserNotifier extends StateNotifier<UserState> {
UserNotifier() : super(UserState(name: "未登录", age: 0, isLogin: false));
// 登录方法(修改状态)
void login(String name, int age) {
state = state.copyWith(name: name, age: age, isLogin: true);
}
// 退出登录方法
void logout() {
state = UserState(name: "未登录", age: 0, isLogin: false);
}
}
// 3. 定义 Provider
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
return UserNotifier();
});
4. FutureProvider:异步状态(用于网络请求、本地存储等异步操作)
适用于异步获取的状态,比如“从接口获取商品列表”“从本地读取用户信息”,自动处理“加载中、成功、失败”三种状态,无需手动管理加载状态。
5. ProviderScope:状态作用域(控制状态的生命周期)
用于定义状态的作用范围,比如“全局状态”(整个 App 可见)、“页面级状态”(仅当前页面及子组件可见),页面销毁时,页面级状态自动释放。
三、实战案例:5个高频场景的最佳实践(多代码+避坑)
结合实际开发中的高频场景,每个案例都包含“需求描述、代码实现、最佳实践说明”,覆盖全局状态、页面级状态、列表状态、表单状态、异步状态,直接套用即可。
案例1:全局状态管理(用户信息+主题模式)
「需求描述」:App 全局共享两个状态——用户信息(登录/退出、用户名、年龄)、主题模式(浅色/深色),所有页面都能访问和修改这两个状态,且退出 App 后状态不丢失(简单模拟持久化)。
「最佳实践」:使用 StateNotifierProvider 管理复杂全局状态,Provider 管理衍生状态,ProviderScope 全局注册,结合 SharedPreferences 模拟持久化。
// 1. 引入依赖(pubspec.yaml)
// flutter_riverpod: ^2.3.0
// shared_preferences: ^2.2.2
// 2. 全局初始化 ProviderContainer(main.dart)
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化 SharedPreferences
final prefs = await SharedPreferences.getInstance();
// 创建全局 ProviderContainer
final container = ProviderContainer(
overrides: [
// 注入 SharedPreferences,供其他 Provider 使用
sharedPreferencesProvider.overrideWithValue(prefs),
],
);
runApp(
// 全局 ProviderScope,让所有组件都能访问容器中的状态
UncontrolledProviderScope(
container: container,
child: const MyApp(),
),
);
}
// 3. 定义 SharedPreferences Provider(供其他 Provider 依赖)
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
throw UnimplementedError("SharedPreferences 需在 main 中注入");
});
// 4. 用户信息状态管理(StateNotifierProvider)
class UserState {
final String name;
final int age;
final bool isLogin;
UserState({required this.name, required this.age, required this.isLogin});
UserState copyWith({String? name, int? age, bool? isLogin}) {
return UserState(
name: name ?? this.name,
age: age ?? this.age,
isLogin: isLogin ?? this.isLogin,
);
}
}
class UserNotifier extends StateNotifier<UserState> {
final Ref ref; // 用于依赖其他 Provider(如 SharedPreferences)
UserNotifier(this.ref) : super(UserState(name: "未登录", age: 0, isLogin: false)) {
// 初始化:从本地读取用户信息
_loadUserInfo();
}
// 从本地读取用户信息
Future<void> _loadUserInfo() async {
final prefs = ref.read(sharedPreferencesProvider);
final name = prefs.getString("user_name") ?? "未登录";
final age = prefs.getInt("user_age") ?? 0;
final isLogin = prefs.getBool("is_login") ?? false;
state = state.copyWith(name: name, age: age, isLogin: isLogin);
}
// 登录(保存到本地+更新状态)
Future<void> login(String name, int age) async {
final prefs = ref.read(sharedPreferencesProvider);
await prefs.setString("user_name", name);
await prefs.setInt("user_age", age);
await prefs.setBool("is_login", true);
state = state.copyWith(name: name, age: age, isLogin: true);
}
// 退出登录(清除本地+重置状态)
Future<void> logout() async {
final prefs = ref.read(sharedPreferencesProvider);
await prefs.clear();
state = UserState(name: "未登录", age: 0, isLogin: false);
}
}
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
return UserNotifier(ref);
});
// 5. 主题模式状态管理(StateProvider,简单状态)
final themeModeProvider = StateProvider<ThemeMode>((ref) {
// 从本地读取主题模式
final prefs = ref.read(sharedPreferencesProvider);
final isDark = prefs.getBool("is_dark") ?? false;
return isDark ? ThemeMode.dark : ThemeMode.light;
});
// 6. 衍生状态:根据用户登录状态,判断是否显示欢迎语(Provider)
final welcomeMessageProvider = Provider((ref) {
final userState = ref.watch(userProvider);
return userState.isLogin ? "欢迎回来,${userState.name}!" : "请登录";
});
// 7. 组件中使用(首页)
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听用户状态
final userState = ref.watch(userProvider);
// 监听主题模式
final themeMode = ref.watch(themeModeProvider);
// 监听衍生状态
final welcomeMessage = ref.watch(welcomeMessageProvider);
return Scaffold(
appBar: AppBar(
title: Text(welcomeMessage),
actions: [
// 切换主题
IconButton(
icon: Icon(themeMode == ThemeMode.dark ? Icons.light_mode : Icons.dark_mode),
onPressed: () {
final isDark = themeMode == ThemeMode.dark;
ref.read(sharedPreferencesProvider).setBool("is_dark", !isDark);
ref.read(themeModeProvider.notifier).state = !isDark ? ThemeMode.dark : ThemeMode.light;
},
),
],
),
body: Center(
child: userState.isLogin
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("用户名:${userState.name}"),
Text("年龄:${userState.age}"),
ElevatedButton(
onPressed: () => ref.read(userProvider.notifier).logout(),
child: const Text("退出登录"),
),
],
)
: ElevatedButton(
onPressed: () => ref.read(userProvider.notifier).login("Flutter开发者", 25),
child: const Text("登录"),
),
),
);
}
}
「最佳实践说明」:
- 全局状态通过“全局 ProviderContainer + UncontrolledProviderScope”注册,所有组件可直接访问,无需嵌套;
- 复杂状态(用户信息)用 StateNotifierProvider,拆分“状态模型+状态逻辑”,逻辑清晰,可维护性强;
- 简单状态(主题模式)用 StateProvider,用法简洁,无需额外定义 StateNotifier;
- 衍生状态(欢迎语)用 Provider,依赖其他状态计算得出,无需单独管理,减少状态冗余;
- 通过 ref 依赖其他 Provider(如 SharedPreferences),实现状态间的解耦,避免硬编码。
案例2:页面级状态管理(购物页筛选状态)
「需求描述」:购物页面有一个筛选状态(全部/热销/低价),仅购物页及子组件(筛选栏、商品列表)需要访问该状态,页面销毁后,筛选状态自动释放,避免内存泄漏。
「最佳实践」:使用 StateNotifierProvider + 局部 ProviderScope,将状态作用域限制在购物页内部,页面销毁时状态自动释放。
// 1. 筛选状态模型+StateNotifier
class FilterState {
final String currentFilter; // 全部/热销/低价
FilterState({this.currentFilter = "全部"});
FilterState copyWith({String? currentFilter}) {
return FilterState(currentFilter: currentFilter ?? this.currentFilter);
}
}
class FilterNotifier extends StateNotifier<FilterState> {
FilterNotifier() : super(FilterState());
// 切换筛选条件
void setFilter(String filter) {
if (state.currentFilter != filter) {
state = state.copyWith(currentFilter: filter);
}
}
}
// 2. 定义页面级 Provider(无需全局注册)
final filterProvider = StateNotifierProvider<FilterNotifier, FilterState>((ref) {
return FilterNotifier();
});
// 3. 购物页(局部 ProviderScope)
class ShoppingPage extends ConsumerWidget {
const ShoppingPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 全局状态:用户信息、购物车数量(从全局容器获取)
final userState = ref.watch(userProvider);
final cartCount = ref.watch(cartProvider).length;
// 局部 ProviderScope:将筛选状态作用域限制在当前页面
return ProviderScope(
child: Scaffold(
appBar: AppBar(
title: Text("购物页 ${userState.name}"),
),
body: Column(
children: [
// 筛选栏组件(依赖筛选状态)
const FilterBar(),
// 购物车数量
Text("购物车数量:$cartCount"),
// 商品列表组件(依赖筛选状态)
const Expanded(child: ProductList()),
],
),
),
);
}
}
// 4. 筛选栏组件(依赖页面级筛选状态)
class FilterBar extends ConsumerWidget {
const FilterBar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final filterState = ref.watch(filterProvider);
final filterNotifier = ref.read(filterProvider.notifier);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => filterNotifier.setFilter("全部"),
style: ElevatedButton.styleFrom(
backgroundColor: filterState.currentFilter == "全部" ? Colors.blue : Colors.grey,
),
child: const Text("全部"),
),
ElevatedButton(
onPressed: () => filterNotifier.setFilter("热销"),
style: ElevatedButton.styleFrom(
backgroundColor: filterState.currentFilter == "热销" ? Colors.blue : Colors.grey,
),
child: const Text("热销"),
),
ElevatedButton(
onPressed: () => filterNotifier.setFilter("低价"),
style: ElevatedButton.styleFrom(
backgroundColor: filterState.currentFilter == "低价" ? Colors.blue : Colors.grey,
),
child: const Text("低价"),
),
],
);
}
}
// 5. 商品列表组件(依赖筛选状态)
class ProductList extends ConsumerWidget {
const ProductList({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final filterState = ref.watch(filterProvider);
// 模拟商品数据(根据筛选条件过滤)
final allProducts = [
{"name": "商品1", "price": 99, "isHot": true},
{"name": "商品2", "price": 59, "isHot": false},
{"name": "商品3", "price": 199, "isHot": true},
{"name": "商品4", "price": 39, "isHot": false},
];
final filteredProducts = switch (filterState.currentFilter) {
"热销" => allProducts.where((p) => p["isHot"] == true).toList(),
"低价" => allProducts.where((p) => p["price"] < 60).toList(),
_ => allProducts,
};
return ListView.builder(
itemCount: filteredProducts.length,
itemBuilder: (context, index) {
final product = filteredProducts[index];
return ListTile(
title: Text(product["name"]!),
subtitle: Text("¥${product["price"]}"),
);
},
);
}
}
「最佳实践说明」:
- 页面级状态通过“局部 ProviderScope”包裹,仅当前页面及子组件可访问,避免状态污染;
- 页面销毁时,ProviderScope 会自动销毁其内部的所有 Provider(filterProvider),无需手动 dispose,彻底避免内存泄漏;
- 页面级状态与全局状态(用户、购物车)分离,各司其职,降低耦合度;
- 筛选逻辑与 UI 组件分离(FilterNotifier 处理逻辑,FilterBar、ProductList 负责展示),符合“单一职责原则”。
案例3:列表状态管理(商品列表+下拉刷新+上拉加载)
「需求描述」:商品列表页面,支持下拉刷新(重新获取数据)、上拉加载更多,需要管理“商品列表、加载状态、分页参数”,且列表项有独立的“选中状态”。
「最佳实践」:用 StateNotifierProvider 管理列表整体状态,用 FamilyProvider 管理单个列表项的选中状态,结合 FutureProvider 处理异步请求。
// 1. 商品模型
class Product {
final int id;
final String name;
final double price;
final bool isHot;
Product({required this.id, required this.name, required this.price, required this.isHot});
}
// 2. 列表状态模型+StateNotifier(管理列表整体状态)
class ProductListState {
final List<Product> products; // 商品列表
final bool isLoading; // 整体加载状态
final bool hasMore; // 是否有更多数据
final int page; // 当前页码
final String errorMsg; // 错误信息
ProductListState({
this.products = const [],
this.isLoading = false,
this.hasMore = true,
this.page = 1,
this.errorMsg = "",
});
ProductListState copyWith({
List<Product>? products,
bool? isLoading,
bool? hasMore,
int? page,
String? errorMsg,
}) {
return ProductListState(
products: products ?? this.products,
isLoading: isLoading ?? this.isLoading,
hasMore: hasMore ?? this.hasMore,
page: page ?? this.page,
errorMsg: errorMsg ?? this.errorMsg,
);
}
}
class ProductListNotifier extends StateNotifier<ProductListState> {
final Ref ref;
ProductListNotifier(this.ref) : super(ProductListState()) {
// 初始化:获取第一页数据
fetchProducts();
}
// 模拟网络请求:获取商品列表
Future<List<Product>> _fetchProducts(int page) async {
await Future.delayed(const Duration(seconds: 1)); // 模拟网络延迟
if (page == 3) {
return []; // 第三页无数据,模拟“没有更多”
}
return List.generate(10, (index) {
final id = (page - 1) * 10 + index + 1;
return Product(
id: id,
name: "商品$id",
price: 39.9 + index * 10,
isHot: index % 2 == 0,
);
});
}
// 下拉刷新:重新获取第一页数据
Future<void> refreshProducts() async {
state = state.copyWith(isLoading: true, errorMsg: "");
try {
final products = await _fetchProducts(1);
state = state.copyWith(
products: products,
page: 1,
hasMore: products.isNotEmpty,
isLoading: false,
);
} catch (e) {
state = state.copyWith(
errorMsg: "刷新失败:${e.toString()}",
isLoading: false,
);
}
}
// 上拉加载更多
Future<void> loadMoreProducts() async {
if (state.isLoading || !state.hasMore) return; // 正在加载或无更多数据,直接返回
state = state.copyWith(isLoading: true, errorMsg: "");
try {
final nextPage = state.page + 1;
final newProducts = await _fetchProducts(nextPage);
state = state.copyWith(
products: [...state.products, ...newProducts],
page: nextPage,
hasMore: newProducts.isNotEmpty,
isLoading: false,
);
} catch (e) {
state = state.copyWith(
errorMsg: "加载失败:${e.toString()}",
isLoading: false,
);
}
}
}
// 3. 列表整体状态 Provider
final productListProvider = StateNotifierProvider<ProductListNotifier, ProductListState>((ref) {
return ProductListNotifier(ref);
});
// 4. 列表项选中状态(FamilyProvider:为每个item生成独立的Provider)
final productSelectedProvider = FamilyProvider<bool, int>((ref, productId) => false);
// 5. 商品列表页面
class ProductListPage extends ConsumerWidget {
const ProductListPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productListState = ref.watch(productListProvider);
final productListNotifier = ref.read(productListProvider.notifier);
return Scaffold(
appBar: const AppBar(title: Text("商品列表")),
body: RefreshIndicator(
onRefresh: productListNotifier.refreshProducts,
child: ListView.builder(
itemCount: productListState.products.length + 1, // +1 用于加载更多提示
itemBuilder: (context, index) {
// 加载更多提示
if (index == productListState.products.length) {
if (productListState.isLoading) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(child: CircularProgressIndicator()),
);
} else if (productListState.errorMsg.isNotEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(child: Text(productListState.errorMsg)),
);
} else if (!productListState.hasMore) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(child: Text("没有更多商品了")),
);
} else {
return const SizedBox.shrink();
}
}
// 商品item
final product = productListState.products[index];
// 获取当前item的选中状态(FamilyProvider,传入productId)
final isSelected = ref.watch(productSelectedProvider(product.id));
final selectedNotifier = ref.read(productSelectedProvider(product.id).notifier);
return ListTile(
title: Text(product.name),
subtitle: Text("¥${product.price}"),
trailing: isSelected ? const Icon(Icons.check, color: Colors.blue) : null,
onTap: () {
// 切换选中状态
selectedNotifier.state = !isSelected;
},
onLongPress: () {
// 模拟加入购物车
ref.read(cartProvider.notifier).addProduct(product);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${product.name} 已加入购物车")),
);
},
);
},
// 上拉加载更多(监听列表滚动到底部)
controller: ScrollController()
..addListener(() {
final controller = ScrollController();
if (controller.position.pixels >= controller.position.maxScrollExtent - 200) {
productListNotifier.loadMoreProducts();
}
}),
),
),
);
}
}
「最佳实践说明」:
- 列表整体状态(商品列表、加载状态、分页)用 StateNotifierProvider 管理,将异步请求、分页逻辑封装在 StateNotifier 中,UI 组件只负责展示;
- 列表项独立状态(选中状态)用 FamilyProvider,为每个 item 生成独立的 Provider,修改单个 item 状态时,仅触发该 item 重建,避免整个列表重建,提升性能;
- 下拉刷新、上拉加载逻辑与 UI 分离,通过 StateNotifier 的方法暴露给组件,降低耦合度;
- 处理加载中、失败、无更多数据三种状态,提升用户体验;
- 通过 ref 依赖其他 Provider(如购物车),实现组件间的通信,无需 Context。
案例4:复杂表单状态管理(登录表单)
「需求描述」:登录表单包含“用户名、密码、验证码、协议勾选”四个状态,需要实现“输入验证、按钮禁用逻辑、表单提交”,且状态变化时实时更新 UI。
「最佳实践」:用 StateNotifierProvider 合并所有表单状态,封装验证逻辑和提交逻辑,衍生状态控制按钮禁用状态,避免多个状态单独管理导致的冗余。
// 1. 表单状态模型
class LoginFormState {
final String username; // 用户名
final String password; // 密码
final String code; // 验证码
final bool isAgreed; // 是否同意协议
final bool isSubmitting; // 是否正在提交
final String errorMsg; // 提交错误信息
LoginFormState({
this.username = "",
this.password = "",
this.code = "",
this.isAgreed = false,
this.isSubmitting = false,
this.errorMsg = "",
});
LoginFormState copyWith({
String? username,
String? password,
String? code,
bool? isAgreed,
bool? isSubmitting,
String? errorMsg,
}) {
return LoginFormState(
username: username ?? this.username,
password: password ?? this.password,
code: code ?? this.code,
isAgreed: isAgreed ?? this.isAgreed,
isSubmitting: isSubmitting ?? this.isSubmitting,
errorMsg: errorMsg ?? this.errorMsg,
);
}
// 表单验证:判断是否可以提交
bool get isFormValid {
return username.isNotEmpty &&
password.length >= 6 &&
code.length == 6 &&
isAgreed;
}
}
// 2. 表单状态逻辑(StateNotifier)
class LoginFormNotifier extends StateNotifier<LoginFormState> {
final Ref ref;
LoginFormNotifier(this.ref) : super(LoginFormState());
// 更新用户名
void updateUsername(String username) {
state = state.copyWith(username: username, errorMsg: "");
}
// 更新密码
void updatePassword(String password) {
state = state.copyWith(password: password, errorMsg: "");
}
// 更新验证码
void updateCode(String code) {
state = state.copyWith(code: code, errorMsg: "");
}
// 切换协议勾选状态
void toggleAgreed() {
state = state.copyWith(isAgreed: !state.isAgreed, errorMsg: "");
}
// 表单提交(模拟网络请求)
Future<void> submit() async {
if (!state.isFormValid) return; // 表单无效,不提交
state = state.copyWith(isSubmitting: true, errorMsg: "");
try {
// 模拟登录请求
await Future.delayed(const Duration(seconds: 1));
if (state.username == "admin" && state.password == "123456") {
// 登录成功:调用用户状态的登录方法
ref.read(userProvider.notifier).login(state.username, 25);
} else {
throw Exception("用户名或密码错误");
}
} catch (e) {
state = state.copyWith(
errorMsg: e.toString(),
isSubmitting: false,
);
} finally {
if (mounted) {
state = state.copyWith(isSubmitting: false);
}
}
}
}
// 3. 表单 Provider
final loginFormProvider = StateNotifierProvider<LoginFormNotifier, LoginFormState>((ref) {
return LoginFormNotifier(ref);
});
// 4. 登录表单页面
class LoginPage extends ConsumerWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formState = ref.watch(loginFormProvider);
final formNotifier = ref.read(loginFormProvider.notifier);
return Scaffold(
appBar: const AppBar(title: Text("登录")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 用户名输入框
TextField(
onChanged: formNotifier.updateUsername,
decoration: InputDecoration(
hintText: "请输入用户名",
errorText: formState.username.isEmpty && formState.errorMsg.isEmpty
? null
: formState.username.isEmpty
? "用户名不能为空"
: null,
),
),
const SizedBox(height: 16),
// 密码输入框
TextField(
obscureText: true,
onChanged: formNotifier.updatePassword,
decoration: InputDecoration(
hintText: "请输入密码(至少6位)",
errorText: formState.password.isNotEmpty && formState.password.length < 6
? "密码至少6位"
: null,
),
),
const SizedBox(height: 16),
// 验证码输入框
TextField(
onChanged: formNotifier.updateCode,
decoration: InputDecoration(
hintText: "请输入6位验证码",
errorText: formState.code.isNotEmpty && formState.code.length != 6
? "验证码必须6位"
: null,
),
),
const SizedBox(height: 16),
// 协议勾选
Row(
children: [
Checkbox(
value: formState.isAgreed,
onChanged: (_) => formNotifier.toggleAgreed(),
),
const Text("同意用户协议和隐私政策"),
],
),
const SizedBox(height: 24),
// 错误提示
if (formState.errorMsg.isNotEmpty)
Text(
formState.errorMsg,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 16),
// 登录按钮
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: formState.isFormValid && !formState.isSubmitting
? formNotifier.submit
: null,
child: formState.isSubmitting
? const CircularProgressIndicator(color: Colors.white)
: const Text("登录"),
),
),
],
),
),
);
}
}
「最佳实践说明」:
- 合并表单所有相关状态,用一个 StateNotifierProvider 管理,避免多个状态单独管理导致的嵌套冗余和逻辑混乱;
- 表单验证逻辑(isFormValid)作为衍生状态,封装在状态模型中,组件直接使用,无需重复计算;
- 提交逻辑封装在 StateNotifier 中,组件只需调用 submit 方法,无需关心内部实现,降低耦合度;
- 处理“正在提交”状态,避免重复提交;处理错误信息,提升用户体验;
- 输入验证实时反馈,用户输入时立即提示错误,提升交互体验。
案例5:异步状态管理(网络请求+本地缓存)
「需求描述」:从接口获取用户个人信息,展示“加载中、成功、失败”三种状态,同时将获取到的用户信息缓存到本地,下次打开页面时优先展示本地缓存,再请求接口更新。
「最佳实践」:用 FutureProvider 处理异步请求,结合 StateNotifierProvider 管理本地缓存,实现“缓存优先、接口更新”的逻辑,提升用户体验。
// 1. 用户信息模型(与全局用户状态一致,可复用)
class UserInfo {
final String name;
final int age;
final String avatar;
final String email;
UserInfo({required this.name, required this.age, required this.avatar, required this.email});
// 从JSON解析
factory UserInfo.fromJson(Map<String, dynamic> json) {
return UserInfo(
name: json["name"],
age: json["age"],
avatar: json["avatar"],
email: json["email"],
);
}
// 转换为JSON(用于本地缓存)
Map<String, dynamic> toJson() {
return {
"name": name,
"age": age,
"avatar": avatar,
"email": email,
};
}
}
// 2. 本地缓存状态管理(StateNotifierProvider)
class UserCacheNotifier extends StateNotifier<UserInfo?> {
final Ref ref;
UserCacheNotifier(this.ref) : super(null) {
// 初始化:从本地读取缓存
_loadCache();
}
// 从本地读取缓存
Future<void> _loadCache() async {
final prefs = ref.read(sharedPreferencesProvider);
final jsonStr = prefs.getString("user_info_cache");
if (jsonStr != null) {
final json = jsonDecode(jsonStr);
state = UserInfo.fromJson(json);
}
}
// 保存缓存到本地
Future<void> saveCache(UserInfo userInfo) async {
final prefs = ref.read(sharedPreferencesProvider);
final jsonStr = jsonEncode(userInfo.toJson());
await prefs.setString("user_info_cache", jsonStr);
state = userInfo;
}
// 清除缓存
Future<void> clearCache() async {
final prefs = ref.read(sharedPreferencesProvider);
await prefs.remove("user_info_cache");
state = null;
}
}
final userCacheProvider = StateNotifierProvider<UserCacheNotifier, UserInfo?>((ref) {
return UserCacheNotifier(ref);
});
// 3. 异步请求用户信息(FutureProvider)
final fetchUserInfoProvider = FutureProvider<UserInfo>((ref) async {
// 模拟网络请求:从接口获取用户信息
final response = await http.get(Uri.parse("https://api.example.com/user/info"));
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
final userInfo = UserInfo.fromJson(json);
// 请求成功后,保存到本地缓存
ref.read(userCacheProvider.notifier).saveCache(userInfo);
return userInfo;
} else {
throw Exception("获取用户信息失败");
}
});
// 4. 个人信息页面
class ProfilePage extends ConsumerWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听本地缓存
final userCache = ref.watch(userCacheProvider);
// 监听异步请求状态
final fetchUserAsync = ref.watch(fetchUserInfoProvider);
return Scaffold(
appBar: const AppBar(title: Text("个人信息")),
body: fetchUserAsync.when(
loading: () {
// 加载中:如果有缓存,展示缓存;否则展示加载中
if (userCache != null) {
return _buildProfileContent(userCache, isCache: true);
} else {
return const Center(child: CircularProgressIndicator());
}
},
error: (error, stackTrace) {
// 失败:如果有缓存,展示缓存+错误提示;否则展示错误提示
if (userCache != null) {
return Column(
children: [
_buildProfileContent(userCache, isCache: true),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
"加载失败:${error.toString()},展示缓存数据",
style: const TextStyle(color: Colors.orange),
),
),
],
);
} else {
return Center(child: Text("获取信息失败:${error.toString()}"));
}
},
data: (userInfo) {
// 成功:展示最新数据
return _buildProfileContent(userInfo, isCache: false);
},
),
);
}
// 构建个人信息内容
Widget _buildProfileContent(UserInfo userInfo, {required bool isCache}) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 头像
CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(userInfo.avatar),
),
const SizedBox(height: 16),
// 用户名
Text("用户名:${userInfo.name}", style: const TextStyle(fontSize: 18)),
const SizedBox(height: 8),
// 年龄
Text("年龄:${userInfo.age}"),
const SizedBox(height: 8),
// 邮箱
Text("邮箱:${userInfo.email}"),
const SizedBox(height: 16),
// 缓存提示
if (isCache)
const Text(
"展示的是缓存数据",
style: TextStyle(color: Colors.grey),
),
],
),
);
}
}
「最佳实践说明」:
- 用 FutureProvider 处理异步请求,自动管理“加载中、成功、失败”三种状态,无需手动管理;
- 用 StateNotifierProvider 管理本地缓存,实现“缓存优先、接口更新”的逻辑,提升用户体验(无网络时也能展示数据);
- 异步请求成功后,自动更新本地缓存,确保缓存与接口数据一致;
- 加载中、失败状态下,优先展示缓存数据,减少用户等待时间,提升交互体验;
- 缓存逻辑与请求逻辑分离,各司其职,可维护性强。
四、Riverpod 2.x 最佳实践总结(避坑指南)
结合前面的案例,总结 Riverpod 2.x 的5个最佳实践,帮你避开常见坑,写出高质量代码。
1. 按状态作用范围选择合适的 Provider
- 全局状态:用 StateNotifierProvider/Provider,通过全局 ProviderContainer 注册,供所有组件访问;
- 页面级状态:用 StateNotifierProvider/StateProvider,通过局部 ProviderScope 注册,页面销毁时自动释放;
- 局部组件状态:用 StateProvider/FamilyProvider,仅在组件内部使用,避免状态污染;
- 异步状态:用 FutureProvider,自动处理加载状态,无需手动管理;
- 衍生状态:用 Provider,依赖其他状态计算得出,减少状态冗余。
2. 复杂状态必用 StateNotifierProvider,拆分“状态模型+逻辑”
对于多值、多逻辑的复杂状态(如列表、表单),不要用多个 StateProvider 单独管理,而是用 StateNotifierProvider,将“状态模型”(不可变对象)和“状态逻辑”(StateNotifier)拆分,逻辑清晰,可维护性强。
3. 避免过度依赖,精准监听状态
- 组件仅监听自身需要的状态,不要监听无关状态,避免不必要的重建;
- 使用 ref.watch 监听状态变化,使用 ref.read 仅获取状态(不监听),比如按钮点击修改状态时,用 ref.read;
- 对于不需要实时监听的状态,可使用 ref.read 或 listen: false,减少重建次数。
4. 合理使用 ProviderScope,控制状态生命周期
全局状态用全局 ProviderContainer + UncontrolledProviderScope,页面级状态用局部 ProviderScope,确保页面销毁时,页面级状态自动释放,避免内存泄漏。
5. 状态不可变,修改状态需返回新对象
Riverpod 推荐使用不可变对象管理状态(如案例中的 UserState、FilterState),修改状态时,不要直接修改原对象,而是通过 copyWith 方法返回新对象,确保状态变化可追踪,避免不可预期的 bug。
五、总结
Riverpod 2.x 的核心价值,在于“解耦、精准、可扩展”——它彻底摆脱了 Provider 的嵌套地狱,通过强类型安全减少 runtime 异常,通过灵活的状态分层管理避免内存泄漏,通过自动缓存优化应用性能。
本文通过5个高频实战案例,覆盖了全局状态、页面级状态、列表状态、表单状态、异步状态,每个案例都包含完整代码和最佳实践说明,希望能帮你快速上手 Riverpod 2.x。
记住:Riverpod 不是“越复杂越好”,而是“合适才好”——根据状态的作用范围、复杂度,选择合适的 Provider,遵循“状态不可变、逻辑与 UI 分离”的原则,就能写出简洁、高效、可维护的 Flutter 代码。