猫哥课堂 ducafecat.com
开通 VIP 会员, 观看所有视频、附件、猫哥密友、猫哥 VIP 群

使用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 todo manage

学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用。通过Consumer组件、ConsumerStatefulWidget类、ref.read方法和provider build重写,了解Riverpod的状态管理和更新状态机制。

参考

https://pub.dev/packages/riverpod

https://riverpod.dev/

https://flutter.ducafecat.com/

知识点

  1. Consumer 组件: Consumer组件是Riverpod提供的用于订阅和监听Provider数据变化的组件。它接收一个或多个Provider,并在数据发生变化时重新构建其子组件。
  2. ConsumerStatefulWidget 类: ConsumerStatefulWidget是一个抽象类,继承自StatefulWidget。通过继承ConsumerStatefulWidget类并实现其createState方法,可以创建一个具有可变状态和自动重建机制的组件。
  3. ref.read 方法: ref.read方法用于从ProviderContainer中获取Provider的值,而无需订阅或监听它。它适用于在不需要实时更新的情况下获取Provider的当前值。
  4. provider build 重写: 通过重写Provider的build方法,可以在Provider的值发生变化时重新构建其依赖项。这使得我们可以控制在数据变化时如何更新UI。
  5. 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 方式读取 provider ref.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列表应用。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥 ducafecat.com

end


Copyright 2024 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3