使用Riverpod在Flutter中创建Todo列表

使用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 方式读取 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/616131.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL前缀索引(3/16)

前缀索引 前缀索引&#xff1a;MySQL支持前缀索引&#xff0c;允许定义字符串的一部分作为索引。如果不指定前缀长度&#xff0c;索引将包含整个字符串。前缀索引可以节省空间&#xff0c;但可能会增加查询时的记录扫描次数&#xff08;因为会查询到多个前缀相同的数据&#x…

动态规划原理及其在优化问题中的应用解析

动态规划原理及其在优化问题中的应用解析 一、最优子结构二、重叠子问题三、何时使用动态规划法四、伪代码示例五、C代码示例七、详细说明动态规划原理7.1、最优子结构7.2 重叠子问题7.3 动态规划的实现 八、结论 动态规划是一种解决优化问题的方法&#xff0c;它通过将原问题分…

JavaScript代码挑战

让我们回到朱莉娅和凯特关于狗的研究。这次&#xff0c;他们想把狗的年龄转换成人的年龄&#xff0c;并计算出研究中狗的平均年龄。 创建一个函数 “calcAverageHumanAge”&#xff0c;该函数接受一个狗的年龄数组&#xff08;‘age’&#xff09;&#xff0c;并按顺序执行以下…

《黑马点评》Redis高并发项目实战笔记(上)P1~P45

P1 Redis企业实战课程介绍 P2 短信登录 导入黑马点评项目 首先在数据库连接下新建一个数据库hmdp&#xff0c;然后右键hmdp下的表&#xff0c;选择运行SQL文件&#xff0c;然后指定运行文件hmdp.sql即可&#xff08;建议MySQL的版本在5.7及以上&#xff09;&#xff1a; 下面这…

Hidl 学习总结 2

1、Android apk 调用Hidl处理 app添加对应的hidl jar包 2、MainActivity 添加如下代码 package com.example.test2;import androidx.appcompat.app.AppCompatActivity; import vendor.hardware.test.V1_0.ITest; import vendor.hardware.test.V1_0.ITestCmdCallback; import …

Fence同步

在《Android图形显示系统》没有介绍到帧同步的相关概念&#xff0c;这里简单介绍补充一下。 在图形显示系统中&#xff0c;图形缓存GraphicBuffer可以被不同的硬件来访问&#xff0c;如CPU、GPU、HWC都可以对缓存进行读写&#xff0c;如果同时对图形缓存进行操作&#xff0c;有…

LeetCode最长有效括号问题解

给定一个仅包含字符的字符串(’ 和 ‘)’&#xff0c;返回最长有效的长度(出色地-形成) 括号子弦。 示例1&#xff1a; 输入&#xff1a;s “(()” 输出&#xff1a;2 说明&#xff1a;最长的有效括号子字符串是 “()” 。 示例2&#xff1a; 输入&#xff1a;s “)()())…

应该如何进行POC测试?—【DBA从入门到实践】第三期

在数据库选型过程中&#xff0c;为确保能够灵活应对数据规模的不断扩大和处理需求的日益复杂化&#xff0c;企业和技术人员会借助POC测试来评估不同数据库系统的性能。在测试过程中&#xff0c;性能、并发处理能力、存储成本以及高可用性等核心要素通常会成为大家关注的焦点&am…

单链表详解(无哨兵位),实现增删改查

1.顺序表对比单链表的缺点 中间或头部插入时&#xff0c;需要移动数据再插入&#xff0c;如果数据庞大会导致效率降低每次增容就需要申请空间&#xff0c;而且需要拷贝数据&#xff0c;释放旧空间增容造成浪费&#xff0c;因为一般都是以2倍增容 2.链表的基础知识 链表也是线…

【从浅学到熟知Linux】环境变量详谈(含使用程序获取环境变量的3种方法、如何查看环境变量)

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程及数据库等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 环境变量基本概念查看环境变量的方法环境变量相关命令环境变量组织方式及获取环境变量的3种方法验…

JVM面试整理--对象的创建和堆

文章目录 对象的创建过程是怎样的?对象在内存中的结构是怎样的&#xff08;专业的叫法&#xff1a;对象的内存布局&#xff09;对象在内存分配时使用的哪种方式&#xff08;有的地方也称为&#xff1a;分配算法&#xff09;知道什么是“指针碰撞”吗&#xff1f;知道什么是“空…

功能测试_订购单检查_判定表

画判定表的步骤&#xff1a; 列出条件 列出动作