使用Riverpod在Flutter中创建Todo列表
视频
https://youtu.be/mlbeSD1KSIo
https://www.bilibili.com/video/BV1jj42197c8/
前言
原文 https://ducafecat.com/blog/flutter-todo-list-with-riverpod-guide-02
学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用。通过Consumer组件、ConsumerStatefulWidget类、ref.read方法和provider build重写,了解Riverpod的状态管理和更新状态机制。
参考
https://pub.dev/packages/riverpod
https://riverpod.dev/
https://flutter.ducafecat.com/
知识点
- Consumer 组件:
Consumer组件是Riverpod提供的用于订阅和监听Provider数据变化的组件。它接收一个或多个Provider,并在数据发生变化时重新构建其子组件。 - ConsumerStatefulWidget 类:
ConsumerStatefulWidget是一个抽象类,继承自StatefulWidget。通过继承ConsumerStatefulWidget类并实现其createState方法,可以创建一个具有可变状态和自动重建机制的组件。 - ref.read 方法:
ref.read方法用于从ProviderContainer中获取Provider的值,而无需订阅或监听它。它适用于在不需要实时更新的情况下获取Provider的当前值。 - provider build 重写:
通过重写Provider的build方法,可以在Provider的值发生变化时重新构建其依赖项。这使得我们可以控制在数据变化时如何更新UI。 - update state 更新状态:
在Riverpod中,状态更新是通过修改可变的Provider值或使用 invalidateSelf 来完成,触发UI的重建。
步骤
实例对象 TodoEntity
lib/entity/todo.dart
class TodoEntity {final int? id;final String? title;final String? description;final bool? completed;TodoEntity({this.id, this.title, this.description, this.completed});Map<String, dynamic> toMap() {return {'id': id,'title': title,'description': description,'completed': completed,};}static TodoEntity fromMap(Map<String, dynamic> map) {return TodoEntity(id: map['id'],title: map['title'],description: map['description'],completed: map['completed'],);}
}
实现 todo provider
lib/provider/todo_list.dart
定义 TodoList provider 类,todo 数据集合
part 'todo_list.g.dart';
class TodoList extends _$TodoList {static List<TodoEntity> items = [];Future<List<TodoEntity>> build() async {return items;}...
实现新增 add 功能
Future<void> addTodo(TodoEntity todo) async {// await http.post(// Uri.https('your_api.com', '/todos'),// // 我们序列化 Todo 对象并将其 POST 到服务器。// headers: {'Content-Type': 'application/json'},// body: jsonEncode(todo.toJson()),// );// 延迟 1 秒await Future.delayed(const Duration(seconds: 1));// 我们将新的 Todo 添加到 items 列表中。items.add(todo);}
Consumer 新增 todo 按钮栏
lib/pages/todo/widgets/bar.dart
TodoBarWidget 类
class TodoBarWidget extends StatelessWidget {const TodoBarWidget({super.key});
Consumer 方式实现关联 riverpod
Widget _buildView() {// Consumer 提供监听功能的小部件return Consumer(builder: (BuildContext context,WidgetRef ref,Widget? widget,) {return Row(mainAxisAlignment: MainAxisAlignment.center,children: [ElevatedButton(onPressed: () {// 事件处理用 read 方式读取 providerref.read(todoListProvider.notifier).addTodo(TodoEntity(description: 'This is a new todo',),);},child: const Text('Add todo'),),],);});}
注意使用 ref.read,虽然 ref.watch 也能用
Widget build(BuildContext context) {return _buildView();}
状态更新两种方式
lib/provider/todo_list.dart
直接设置新状态值
Future<void> addTodo(TodoEntity todo) async {...// 直接设置 state 值state = AsyncData(items);}
将本地缓存标记为脏
Future<void> addTodo(TodoEntity todo) async {...// 将本地缓存标记为脏ref.invalidateSelf();// 重新构建 TodoList, 呼叫 build 方法await future;}
ConsumerWidget 监听 todo 列表
lib/pages/todo/widgets/list.dart
TodoListWidget 类 继承 ConsumerWidget
class TodoListWidget extends ConsumerWidget {const TodoListWidget({super.key});
监听 todo 列表
Widget _buildList(ref) {var todos = ref.watch(todoListProvider);return switch (todos) {AsyncData<List<TodoEntity>>(:final value) => ListView.builder(itemCount: value.length,itemBuilder: (context, index) {return ListTile(title: Text(value[index].description ?? ""),subtitle: Text((value[index].completed ?? false) ? "yes" : "no"),);},),AsyncError() => const Text('Oops, something unexpected happened'),_ => const CircularProgressIndicator(),};}
Widget build(BuildContext context, WidgetRef ref) {return _buildList(ref);}
build 回调会有一个 ref 对象,直接传入 _buildList 使用
todo 主界面实现
lib/pages/todo/index.dart
TodoPage 类,使用 StatelessWidget 就行
class TodoPage extends StatelessWidget {const TodoPage({super.key});
界面 build
Widget _buildView(BuildContext context) {return const Center(child: Column(children: <Widget>[// StatelessWidget 的组件TodoBarWidget(),// StatefulWidget 的组件// TodoBarStfWidget(),// ConsumerWidget 的列表Expanded(child: TodoListWidget(),),],),);}
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Todo List')),body: _buildView(context),);}
加入启动菜单
lib/pages/index.dart
Widget _buildView(BuildContext context) {return Center(..._buildBtn(context, '03 TODO 列表', const TodoPage()),],),);}
ConsumerStatefulWidget 界面 UI 交互
编写 TodoBarStfWidget 继承 ConsumerStatefulWidget
可以用插件 Flutter Riverpod Snippets 简化输入代码块
https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets
class TodoBarStfWidget extends ConsumerStatefulWidget {const TodoBarStfWidget({super.key});ConsumerState<ConsumerStatefulWidget> createState() =>_TodoBarStfWidgetState();
}class _TodoBarStfWidgetState extends ConsumerState<TodoBarStfWidget> {
定义 Future 状态对象监听变化
// 待处理的 addTodo 操作。如果没有待处理的,则为 null。Future<void>? _pendingAddTodo;
通过 FutureBuilder 更新 UI
Widget _buildView() {return FutureBuilder(// 我们监听待处理的操作,以相应地更新 UI。future: _pendingAddTodo,builder: (context, snapshot) {// 计算是否存在错误状态。// 检查 connectionState 用于在重试操作时进行处理。// 是否错误final isErrored = snapshot.hasError &&snapshot.connectionState != ConnectionState.waiting;// 是否等待final isWaiting = snapshot.connectionState == ConnectionState.waiting;return Row(mainAxisAlignment: MainAxisAlignment.center,children: [// 按钮ElevatedButton(style: ButtonStyle(// 如果出现错误,我们会将该按钮显示为红色backgroundColor: MaterialStateProperty.all(isErrored ? Colors.red : null,),),// 设置 null 后按钮灰色禁止点击onPressed: isWaiting == true? null: () {// 我们将 addTodo 返回的 future 保存在变量中final future = ref.read(todoListProvider.notifier).addTodo(TodoEntity(description: 'This is a new todo'));// 我们将这个 future 存储在本地的状态中setState(() {_pendingAddTodo = future;});},child: const Text('Add todo V2'),),// 操作正在等待,让我们显示一个进度指示器if (isWaiting) ...[const SizedBox(width: 8),const CircularProgressIndicator(),]],);},);}
Widget build(BuildContext context) {return _buildView();}
代码
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_riverpod
小结
本文介绍了如何使用Riverpod在Flutter中实现一个Todo列表功能。通过Consumer组件和ConsumerStatefulWidget类,我们可以订阅和监听数据变化,并在需要时更新UI。使用ref.read方法可以获取Provider的值,从而实现数据的读取和操作。通过provider build重写,我们可以在数据变化时重新构建UI。这篇文章帮助读者更深入地了解了如何使用Riverpod进行状态管理,并实现了一个基本的Todo列表应用。
感谢阅读本文
如果有什么建议,请在评论中让我知道。我很乐意改进。
© 猫哥
ducafecat.com
end