Flutter开发 -- 使用Riverpod实现状态管理

4,125 阅读7分钟

Riverpod也是Provider的开发者,他之所以开发Riverpod也是为了解决Provider的一些问题,比如嵌套Provider的问题,运行时异常的问题,等。同时Riverpod还改进了依赖注入的问题。
对于日常开发来说,riverpod还引入了很多代码生成的机制这也让开发方便了很多。

和Provider的模式类似,Riverpod也需要发出变更的Provider、处理变更的Consumer,还需要一个把两者连在一起的桥梁ProviderScope。在Riverpod里,- 要定义一个Provider只需要定义一个带有@riverpod注解的方法(provider),或者类(Notifier)。

  • 处理变更的只需使用Consumer widget或者继承ConsumerWidget 。
  • 连接两者的桥梁只需要使用ProviderScope。

ProviderScope

连接Provider发出的变更和变更消费者Consumer之间的桥梁。在App的根部Widget定义就好:
你的App里面定义什么都无所谓,在外面包上一层ProviderScope就好。

Provider

理论上来说还是有很多种的Provider,但是在定义的时候没有那么多Provider需要定义了,只需要定义一个。如:总结一下,定义一个Provider需要:

  1. 一个@riverpod注解
  2. 一个方法,方法的参数是:首字母大写的方法名+Ref
  3. 返回点什么

还可以定义一个类来发出变更。与Provider相对的,这个类叫做Notifier。后文会详细解释。

Consumer

对于状态的变更,需要在UI上做出反应。这个角色就有Consumer来承担,或者在riverpod里有更加方便的:可以继承ConsumerWidget

了解这些基础之后,我们来看一个真实的例子。现在回到todo app。在todo首页需要从服务端获取todo列表。第一个Provider就来处理这个功能。

Riverpod在Todo app里的应用

首先需要请求后端api活着的todo的列表。
那么开始安装的步骤:

或者直接修改pubspec.yaml文件: 修改好文件之后,运行flutter pub get命令。

注意,这是可以开启代码自动生成工具:flutter pub run build_runner watch。不开启这个的话,之后会遇到些许的问题。

还有,在开发中会用到实体类,这些难免需要和Json互相转换。这里也有一些代码的自动生成工具,可以考虑使用这些工具。比如:Freezed
由于有了代码自动生成的加持,现在写riverpod的各provider非常的简单。基本上不用去关心当前是那种场景,需要用到那种Provider了。

第一个Provider

在正式开始之前,确保你已经在配置好了ProviderScope。

按照我们之前的定义一个Provider的规则来定义这个获取todo列表的Provider:
这样写完之后如果报错的话,那么是你没有开代码自动生成和没有引入生成的文件。引入生成的文件:part 'list_service.g.dart';。 在一开始开发的时候,除了开启代码自动生成之外就需要引入会生成的文件。

第一个Consumer

现在有了发出变更的Provider,就该有处理变更的Consumer出场了。
以下代码中很多无关紧要的部分已经被略掉了。这样有助于我们把注意力几种在关注点上。在这个StatelessWidget的build方法里直接返回这个Consumer。在Consumerbuilder里的第二个参数是ref,使用ref就可以访问我们定义好的Provider

这里我们用的是watch,它会立刻执行provider里的代码,也就是访问后端获得todo列表。并且在Provider发生变更后刷新界面。与watch相对的还有listenread。知道就好,暂且不谈。

Consumer中,数据显然不会立刻出现在界面中。其中会经过加载状态,然后会获得数据,或者也有可能会出现错误。一个良好的界面反馈是把这些状态都告诉用户。所以,在Scaffoldbody中获取数据的不同状态:就是那个switch语句:

  • AsyncData匹配到的时候,正常显示todo列表页。
  • AsyncError匹配到的时候,说明出现错误了,显示错误信息。
  • 前两者都没有匹配到的时候,说明数据正在加载中,显示加载页面:CircularProgressIndicator

添加一个Todo

目前看来Riverpod还是很简单的,在代码生成的加持下也少了很多的“负重”代码。定义Provider和使用Consumer也有章可循。

添加一个todo的操作细节是这样的:

点击右下的Floating按钮之后会弹出一个AlertDialog。输入新的todo的内容后点击OK按钮完成新建操作。

现在来看看增加一个Todo需要做些什么。这次需要用到Notifier了。使用一个Notifier来实现更新数据的功能。首先来看看如何定义一个Notifier:

  • 你的Notifier名称 extends _$你的Notifier名称。
  • 每个Notifier都必须实现build方法。
  • 定义个Notifier的其他方法。

一个notifier相当于是一个有state的provider。这一点稍后会详细解释。现在来看看我们定义的notifier是什么样的: build方法里获取todo列表数据。定义了一个方法addTodo来添加todo。在addTodo方法中,成功的添加了todo之后,调用ref.invalidateSelf(),并等待状态更新:await future

在addTodo中,成功添加Todo之后为什么要调用invalidateSelf方法呢。还记得前面的一句话不:Notifier是有state的Provider。调用ref.invalidateSelf()就是告诉当前的Notifier实例状态已经“脏”了,需要重新调用notifier的build方法,并且在调用之后通知监听者们有新的状态(发生变更)了。

因为当前notifier的build方法被调用,一个新的get请求会发送到后端来获取新的todo列表。综合来说,当添加一个todo结束之后,调用ref.invalidateSelf(),会出发当前的notifier再次调用build方法请求后端的todo列表。并且在todo 列表页重新绘制这些数据。

更新一个Todo

在上回定义好的notifier里定义一个更新todo的方法:转存失败,建议直接上传图片文件
在这个方法里,常规的先把需要更新的todo发送到后端去更新数据。
在接收到返回的时候需要处理可能的错误:

  • 返回的不是200,等正确的http状态码。
  • 返回是后端定义的错误码。

这两种情况都直接抛出异常,最好是自己定义异常,这里为了简单就直接抛出了Exception。

如果没有发生什么异常情况,那么更新本地的状态。这里用的是不可变对象的方式来更新本地状态。

之后,在详情页增加更新todo的功能。界面操作看起来是这样的:

操作步骤基本上可以概括为,点击一个todo,跳转到详情页。在详情页,点击那个todo的文本会转换为TextField,直接编辑。之后点击OK按钮。更新成功之后TextField变成之前的文本控件,并显示更新之后的todo内容。点击回退可以跳转到todo列表页。

这次选用FutureBuilder来显更新一个todo的不同状态。FutureBuilder的外面也需要一个StatefulWidget才行。对于StatefulWidget的使用这里就不过多描述了。主要看FutureBuilder和Notifier怎么放在一起使用。

一些无关简要的代码已经略掉。需要注意的是:

  1. 给FutreBuilder的这个future需要准备好,也就是代码里的_pendingTodo。
  2. 通过FutureBuilder的snapshot来判断当前的future的执行状态,是进行中,完成出错了,还是正常完成。当然,这需要结合枚举ConnectionState。)
  3. 开始执行更新并把future赋值给_pendingTodo。 赋值给_pendingTodo,是通过setState的方式实现的。

代码

代码在这里!