精通 Flutter 状态管理:从 Provider 到 Riverpod 的全维度实战
首先创建,定义不可变的 Todo 实体类(Flutter 中推荐使用不可变对象管理状态,避免意外的状态篡改):dart// 全局UUID生成器// Todo状态枚举// Todo实体类(不可变)// 唯一标识// 待办内容// 完成状态// 构造函数(强制必填参数,id可选,默认自动生成)Todo({String?id,?uuid.v4();// 复制方法(不可变对象更新状态的标准方式)Strin
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
Flutter 作为跨平台开发框架的标杆,其核心优势在于 “一次编码,多端运行”,但状态管理始终是开发者绕不开的核心难点。从早期的 setState 到 Provider、Bloc,再到如今的 Riverpod,Flutter 状态管理方案一直在向 “更简洁、更安全、更可测试” 演进。本文将以 Riverpod 2.0 为核心,从零构建一个响应式 TodoList 应用,不仅详解核心 API 的使用逻辑,还会拆解 Flutter 响应式编程的底层思维,让你既能写出严谨的代码,又能理解背后的设计哲学。
一、为什么选择 Riverpod 2.0?
在深入代码之前,我们先厘清一个问题:为什么放弃传统 Provider 而选择 Riverpod?
- 彻底解决 Provider 的 “上下文依赖”:Provider 必须依托 BuildContext 获取状态,而 Riverpod 通过 “全局提供者 + 局部消费” 的模式,摆脱了上下文束缚,代码可测试性大幅提升;
- 编译时安全:Riverpod 通过静态类型检查避免了 Provider 常见的 “找不到对应 provider” 运行时错误;
- 更灵活的状态控制:支持多提供者监听、状态自动缓存、手动刷新等高级特性;
- 与 Flutter 3.x 无缝兼容:完全适配空安全,支持 Dart 3.0 的新特性。
本文所有代码基于以下环境:
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 的核心优势体现
无上下文依赖
Riverpod 彻底摆脱了 Flutter 传统状态管理方案对 BuildContext 的依赖。这种设计带来了诸多便利:
- 测试简化:在单元测试中可以直接操作状态,例如
ref.read(todoListProvider.notifier).addTodo("测试"),无需构建复杂的 Widget 树 - 业务逻辑复用:状态操作可以在任何地方执行,包括 isolate、后台任务等非 UI 环境
- 代码组织清晰:避免了上下文层层传递的麻烦,使代码更加模块化
状态隔离
Riverpod 的 Provider 设计实现了完美的关注点分离:
- 独立作用域:每个 Provider 都有明确的作用范围,不会意外影响其他状态
- 测试友好:可以单独测试某个 Provider 的逻辑,无需搭建完整应用环境
- UI 解耦:状态变更逻辑完全独立于 Widget,例如:
// 业务逻辑层 class TodoListNotifier extends StateNotifier<List<Todo>> { void addTodo(String content) { state = [...state, Todo(content: content)]; } } // UI 层 Consumer(builder: (context, ref, _) { final todos = ref.watch(todoListProvider); return ListView.builder(...); })
派生状态自动更新
Riverpod 的响应式系统能智能处理状态依赖关系:
- 自动追踪:当定义
final completedTodoCountProvider = Provider((ref) {...})时,系统会自动建立与todoListProvider的依赖关系 - 高效更新:只有相关状态变化时才会触发重新计算,避免不必要的重建
- 组合灵活:可以轻松创建基于多个状态的派生状态,例如:
final importantTodosProvider = Provider((ref) { final todos = ref.watch(todoListProvider); final filter = ref.watch(filterProvider); return todos.where((t) => t.important && t.matches(filter)).toList(); });
4.2 扩展方向
持久化存储
集成本地存储方案实现数据持久化:
- 初始化加载:在 Notifier 的初始化中读取本地数据
class TodoListNotifier extends StateNotifier<List<Todo>> { TodoListNotifier() : super([]) { _loadTodos(); } Future<void> _loadTodos() async { final prefs = await SharedPreferences.getInstance(); state = loadFromJson(prefs.getString('todos')); } } - 自动同步:在状态变更时自动保存
void addTodo(String content) { state = [...state, Todo(content: content)]; _saveTodos(); // 自动触发保存 }
网络请求整合
使用 FutureProvider 处理异步数据:
final remoteTodosProvider = FutureProvider<List<Todo>>((ref) async {
final response = await http.get(Uri.parse('https://api.example.com/todos'));
return parseTodos(response.body);
});
// UI 中使用 AsyncValue 处理加载状态
Consumer(builder: (context, ref, _) {
final asyncTodos = ref.watch(remoteTodosProvider);
return asyncTodos.when(
loading: () => CircularProgressIndicator(),
error: (err, _) => Text('Error: $err'),
data: (todos) => ListView.builder(...),
);
})
状态防抖/节流
防止用户快速操作导致的状态问题:
void addTodo(String content) {
// 最后一次调用后500ms才实际执行
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 500), () {
state = [...state, Todo(content: content)];
});
}
主题切换
实现动态主题管理:
- 定义主题状态
final themeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.light); - 在 MaterialApp 中应用
Consumer(builder: (context, ref, _) { final themeMode = ref.watch(themeProvider); return MaterialApp( themeMode: themeMode, theme: lightTheme, darkTheme: darkTheme, ); }) - 切换主题
ref.read(themeProvider.notifier).state = ThemeMode.dark;
五、总结
本文以 Riverpod 2.0 为核心,从零构建了一个规范、可扩展的 TodoList 应用,覆盖了 Flutter 状态管理的核心知识点:
- 不可变数据模型的设计思路;
- Riverpod 的核心 Provider 类型(StateNotifierProvider/Provider);
- 状态的不可变更新原则;
- UI 组件与状态的解耦设计。
Flutter 状态管理的本质是 “状态的统一管理与高效分发”,Riverpod 通过简洁的 API 和严格的类型检查,让状态管理从 “玄学” 变成 “工程化实践”。希望本文能帮助你理解 Riverpod 的核心逻辑,也能为你后续的 Flutter 项目提供可复用的代码范式。
最后,附上完整项目的核心亮点:
- ✅ 严格遵循空安全规范;
- ✅ 状态与 UI 完全解耦;
- ✅ 不可变状态更新;
- ✅ 完整的异常处理(空内容过滤);
- ✅ 性能优化(ListView.builder、唯一 Key)。
更多推荐



所有评论(0)