欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
Flutter 作为 Google 推出的跨平台开发框架标杆,其核心优势在于采用 "一次编码,多端运行" 的现代化开发范式,通过高性能的 Skia 渲染引擎和响应式框架设计,能够同时构建 iOS、Android、Web 和桌面端应用。但状态管理始终是 Flutter 开发者绕不开的核心难点,这直接关系到应用的健壮性、可维护性和开发效率。
从早期的 setState 基础方案到 InheritedWidget 的 Provider,再到基于事件驱动的 Bloc 架构,直至如今官方推荐的 Riverpod 方案,Flutter 状态管理生态一直在向 "更简洁、更安全、更可测试" 的方向演进。这种演进路径清晰地反映了框架设计哲学的转变:从命令式编程转向声明式编程,从显式状态传递转向隐式依赖注入,从全局状态管理转向精确的状态作用域控制。
本文将基于最新发布的 Riverpod 2.0 版本,从零开始构建一个完整的响应式 TodoList 应用。我们将从以下维度展开深度解析:
- 详解 Riverpod 的核心 API 使用逻辑,包括 Provider 的多种变体(Provider/StateProvider/FutureProvider等)及其适用场景
- 拆解 Flutter 响应式编程的底层思维,分析 Widget 重建机制与状态变化的因果关系链
- 实践测试驱动开发(TDD)模式,演示如何为状态管理代码编写单元测试和 Widget 测试
- 深入 Riverpod 的源码设计,理解其如何通过编译时代码生成实现类型安全和更好的开发体验
通过这个案例,你将不仅能掌握 Riverpod 的实战技巧,更能深入理解 Flutter 响应式编程的设计哲学,从而在复杂应用开发中做出更合理的技术决策。我们还将对比 Riverpod 与 Bloc、GetX 等方案的性能差异和适用场景,帮助你在实际项目中做出最优选择。
一、为什么选择 Riverpod 2.0?
在深入代码之前,我们先厘清一个问题:为什么放弃传统 Provider 而选择 Riverpod?
编译时安全保障:
更灵活的状态控制机制:
与 Flutter 3.x 的深度集成:
彻底解决 Provider 的 "上下文依赖"问题:
- Provider 的局限性:传统 Provider 必须通过 BuildContext 获取状态,导致以下问题:
- 在非 Widget 类中难以使用(如纯 Dart 业务逻辑层)
- 测试时需要模拟 BuildContext
- 在 initState 等生命周期中无法直接访问
- Riverpod 的解决方案:
- 采用"全局提供者 + 局部消费"的架构模式
- 提供者可在任何位置创建(不依赖 Widget 树)
- 通过 ProviderReference 或 ProviderContainer 获取状态
- 示例:可直接在数据层定义
final userProvider = StateProvider((ref) => User())
- 传统 Provider 的问题:
- 依赖字符串名称查找 Provider
- 容易出现拼写错误导致的运行时异常
- Riverpod 的改进:
- 完全基于 Dart 的静态类型系统
- 所有 Provider 都是强类型定义
- IDE 能提供自动补全和类型检查
- 示例:尝试访问不存在的 Provider 时,编译器会直接报错
- 多提供者监听:
- 支持通过
ref.watch同时监听多个 Provider - 自动处理依赖关系更新
- 支持通过
- 状态缓存策略:
- 提供自动化的状态缓存
- 支持手动设置缓存失效时间
- 高级刷新控制:
- 提供
ref.refresh()方法强制刷新 - 支持异步状态自动重建
- 示例场景:下拉刷新时调用
ref.refresh(userProvider)
- 提供
- 空安全支持:
- 完全适配 Dart 的空安全特性
- 提供 Nullable 和 Non-nullable 的状态管理方案
- Dart 3.0 新特性:
- 支持 Records 和 Patterns 等新语法
- 适配新的类修饰符系统
- 性能优化:
- 针对 Flutter 3.x 的渲染管线优化
- 更高效的状态变更通知机制
- 示例:可以使用
switch表达式处理不同 Provider 状态
本文所有代码基于以下环境:
plaintext
Flutter 3.16.0 Dart 3.2.0 flutter_riverpod: ^2.4.9 hooks_riverpod: ^2.4.9 (可选,简化状态消费)二、项目初始化与核心概念拆解
2.1 项目结构设计
一个规范的 Flutter 项目应遵循 “关注点分离” 原则,我们的 TodoList 项目结构如下:
plaintext
lib/ ├── main.dart # 入口文件,初始化Riverpod ├── providers/ # 状态提供者目录 │ └── todo_providers.dart # Todo相关状态管理 ├── models/ # 数据模型目录 │ └── todo_model.dart # Todo实体类 ├── widgets/ # 自定义组件目录 │ ├── todo_input.dart # 待办输入组件 │ └── todo_list.dart # 待办列表组件 └── screens/ # 页面目录 └── todo_screen.dart # 主页面2.2 核心依赖引入
在pubspec.yaml中添加依赖:
yaml
dependencies: flutter: sdk: flutter flutter_riverpod: ^2.4.9 hooks_riverpod: ^2.4.9 # 可选,使用HookConsumerWidget简化代码 uuid: ^4.3.3 # 生成唯一Todo ID三、核心代码实现与深度解析
3.1 第一步:定义 Todo 数据模型
首先创建models/todo_model.dart,定义不可变的 Todo 实体类(Flutter 中推荐使用不可变对象管理状态,避免意外的状态篡改):
dart
import 'package:uuid/uuid.dart'; // 全局UUID生成器 const uuid = Uuid(); // Todo状态枚举 enum TodoStatus { pending, completed } // Todo实体类(不可变) class Todo { // 唯一标识 final String id; // 待办内容 final String content; // 完成状态 final TodoStatus status; // 构造函数(强制必填参数,id可选,默认自动生成) Todo({ String? id, required this.content, this.status = TodoStatus.pending, }) : id = id ?? uuid.v4(); // 复制方法(不可变对象更新状态的标准方式) Todo copyWith({ String? id, String? content, TodoStatus? status, }) { return Todo( id: id ?? this.id, content: content ?? this.content, status: status ?? this.status, ); } }代码解析:
- 使用
uuid生成唯一 ID,避免列表操作时的 key 冲突; - 枚举
TodoStatus规范状态值,避免魔法字符串; copyWith方法是不可变对象的核心:每次更新状态都会返回新对象,而非修改原对象,这符合 Flutter 的 “不可变状态” 设计理念,也能让 Riverpod 精准监听状态变化。
3.2 第二步:实现 Riverpod 状态提供者
创建providers/todo_providers.dart,这是整个应用的状态核心:
dart
import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/todo_model.dart'; // 1. 定义待办列表状态提供者(StateNotifierProvider) // 泛型说明:第一个参数是StateNotifier,第二个是状态类型(List<Todo>) final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) { return TodoListNotifier(); }); // 2. 状态管理类(继承StateNotifier) class TodoListNotifier extends StateNotifier<List<Todo>> { // 初始化状态为空列表 TodoListNotifier() : super([]); // 添加待办 void addTodo(String content) { if (content.trim().isEmpty) return; // 空内容过滤 // 不可变更新:创建新列表,添加新Todo state = [ ...state, Todo(content: content), ]; } // 切换待办状态 void toggleTodoStatus(String todoId) { // 不可变更新:遍历列表,匹配ID后更新状态 state = state.map((todo) { if (todo.id == todoId) { return todo.copyWith( status: todo.status == TodoStatus.pending ? TodoStatus.completed : TodoStatus.pending, ); } return todo; }).toList(); } // 删除待办 void deleteTodo(String todoId) { // 不可变更新:过滤掉指定ID的Todo state = state.where((todo) => todo.id != todoId).toList(); } // 清空所有待办 void clearAllTodos() { state = []; } } // 3. 派生状态:已完成的待办数量(Provider) // 基于todoListProvider的状态计算派生状态 final completedTodoCountProvider = Provider<int>((ref) { // 监听todoListProvider的状态变化 final todos = ref.watch(todoListProvider); // 计算已完成数量 return todos.where((todo) => todo.status == TodoStatus.completed).length; });代码解析:
StateNotifierProvider是 Riverpod 中管理 “可变更状态” 的核心 Provider:- 第一个泛型参数
TodoListNotifier是自定义的状态管理类,负责处理状态逻辑; - 第二个泛型参数
List<Todo>是状态的具体类型;
- 第一个泛型参数
TodoListNotifier继承StateNotifier,必须通过state属性更新状态,且必须保证状态的不可变性(因此我们始终创建新列表,而非修改原列表);completedTodoCountProvider是 “派生状态”:基于已有状态计算新值,无需手动管理,当todoListProvider的状态变化时,该 Provider 会自动重新计算。
3.3 第三步:实现 UI 组件
3.3.1 待办输入组件(widgets/todo_input.dart)
dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/todo_providers.dart'; class TodoInput extends ConsumerStatefulWidget { const TodoInput({super.key}); @override ConsumerState<TodoInput> createState() => _TodoInputState(); } class _TodoInputState extends ConsumerState<TodoInput> { // 输入控制器 final TextEditingController _controller = TextEditingController(); @override void dispose() { _controller.dispose(); // 释放资源,避免内存泄漏 super.dispose(); } // 提交待办 void _submitTodo() { final content = _controller.text; // 调用状态提供者的addTodo方法 ref.read(todoListProvider.notifier).addTodo(content); // 清空输入框 _controller.clear(); // 收起键盘 FocusScope.of(context).unfocus(); } @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: TextField( controller: _controller, decoration: const InputDecoration( hintText: "请输入待办内容...", border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), onSubmitted: (_) => _submitTodo(), // 回车提交 ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _submitTodo, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), child: const Text("添加"), ), ], ); } }代码解析:
- 使用
ConsumerStatefulWidget替代普通StatefulWidget,可以直接通过ref访问 Riverpod 提供者; ref.readvsref.watch:ref.watch:监听状态变化,状态更新时重建 Widget;ref.read:仅读取 / 调用方法,不监听状态,适合事件处理(如按钮点击);
- 重写
dispose方法释放TextEditingController,这是 Flutter 开发的 “必做项”,避免内存泄漏。
3.3.2 待办列表组件(widgets/todo_list.dart)
dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/todo_model.dart'; import '../providers/todo_providers.dart'; class TodoList extends ConsumerWidget { const TodoList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // 监听待办列表状态(状态变化时自动重建) final todos = ref.watch(todoListProvider); if (todos.isEmpty) { return const Center( child: Text( "暂无待办事项,添加一个吧!", style: TextStyle(fontSize: 16, color: Colors.grey), ), ); } return ListView.builder( shrinkWrap: true, // 适配父组件高度 itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( key: Key(todo.id), // 唯一key,优化列表性能 leading: Checkbox( value: todo.status == TodoStatus.completed, onChanged: (_) { // 切换待办状态 ref.read(todoListProvider.notifier).toggleTodoStatus(todo.id); }, ), title: Text( todo.content, style: TextStyle( decoration: todo.status == TodoStatus.completed ? TextDecoration.lineThrough : TextDecoration.none, color: todo.status == TodoStatus.completed ? Colors.grey : null, ), ), trailing: IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () { // 删除待办 ref.read(todoListProvider.notifier).deleteTodo(todo.id); }, ), ); }, ); } }代码解析:
ConsumerWidget是无状态组件消费 Riverpod 状态的最佳方式,通过build方法的WidgetRef参数访问状态;ListView.builder是长列表的最优选择,仅构建可视区域的 item,避免性能问题;Key(todo.id)为每个列表项设置唯一 key,Flutter 通过 key 识别列表项的变化,避免重建整个列表。
3.4 第四步:组装主页面
创建screens/todo_screen.dart:
dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/todo_providers.dart'; import '../widgets/todo_input.dart'; import '../widgets/todo_list.dart'; class TodoScreen extends ConsumerWidget { const TodoScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // 监听已完成待办数量 final completedCount = ref.watch(completedTodoCountProvider); // 监听总待办数量 final totalCount = ref.watch(todoListProvider).length; return Scaffold( appBar: AppBar( title: const Text("Riverpod TodoList"), actions: [ // 已完成数量展示 Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text("已完成:$completedCount/$totalCount"), ), // 清空按钮 IconButton( icon: const Icon(Icons.clear_all), onPressed: () { ref.read(todoListProvider.notifier).clearAllTodos(); }, ), ], ), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ const TodoInput(), const SizedBox(height: 16), // 列表区域(占满剩余空间) Expanded( child: const TodoList(), ), ], ), ), ); } }代码解析:
- 页面通过
ref.watch同时监听completedTodoCountProvider和todoListProvider,实现实时的数量统计; Expanded包裹TodoList,让列表占满剩余空间,避免布局溢出;- AppBar 的
actions区域整合了 “数量展示” 和 “清空按钮”,符合用户操作习惯。
3.5 第五步:入口文件配置
修改main.dart,初始化 Riverpod:
dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'screens/todo_screen.dart'; void main() { runApp( // 必须用ProviderScope包裹根组件,才能使用Riverpod const ProviderScope( child: MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Riverpod Todo', theme: ThemeData( primarySwatch: Colors.blue, useMaterial3: true, // 启用Material3设计 ), home: const TodoScreen(), debugShowCheckedModeBanner: false, // 隐藏调试横幅 ); } }代码解析:
ProviderScope是 Riverpod 的核心容器,所有使用 Riverpod 的组件必须在其内部;- 启用
useMaterial3适配最新的 Material Design 3 规范,提升 UI 美观度。
四、核心特性与扩展思考
4.1 Riverpod 的核心优势体现
- 无上下文依赖:所有状态操作无需传递 BuildContext,比如在测试中可以直接调用
ref.read(todoListProvider.notifier).addTodo("测试"),无需构建 Widget 树; - 状态隔离:每个 Provider 都是独立的,状态逻辑与 UI 完全分离,便于单元测试;
- 派生状态自动更新:当待办列表变化时,
completedTodoCountProvider会自动重新计算,无需手动通知更新。
4.2 扩展方向
- 持久化存储:集成
hive或shared_preferences,在TodoListNotifier的初始化和状态更新时读写本地数据; - 网络请求整合:通过
FutureProvider实现待办数据的远程同步; - 状态防抖 / 节流:在添加待办时增加防抖,避免快速点击重复添加;
- 主题切换:通过
StateProvider管理主题模式,实现深色 / 浅色模式切换。
五、总结
本文以 Riverpod 2.0 为核心,从零构建了一个规范、可扩展的 TodoList 应用,覆盖了 Flutter 状态管理的核心知识点:
- 不可变数据模型的设计思路;
- Riverpod 的核心 Provider 类型(StateNotifierProvider/Provider);
- 状态的不可变更新原则;
- UI 组件与状态的解耦设计。
Flutter 状态管理的本质是 “状态的统一管理与高效分发”,Riverpod 通过简洁的 API 和严格的类型检查,让状态管理从 “玄学” 变成 “工程化实践”。希望本文能帮助你理解 Riverpod 的核心逻辑,也能为你后续的 Flutter 项目提供可复用的代码范式。
最后,附上完整项目的核心亮点:
- ✅ 严格遵循空安全规范;
- ✅ 状态与 UI 完全解耦;
- ✅ 不可变状态更新;
- ✅ 完整的异常处理(空内容过滤);
- ✅ 性能优化(ListView.builder、唯一 Key)。