Flutter Riverpod 3.0 发布,大规模重构下的全新状态管理框架

在之前的 《注解模式下的 Riverpod 有什么特别之处》我们聊过 Riverpod 2.x 的设计和使用原理,同时当时我们就聊到作者已经在开始探索 3.0 的重构方式,而现在随着 Riverpod 3.0 的发布,riverpod 带来了许多细节性的变化。

当然,这也带来了需要使用方式上的变动。

废话不多说,首先 Riverpod 3.0 与 2.0 的对比,新增还的功能有:

  • 自动重试失败的 Provider: 这是 3.0 的一个核心特性,当一个 Provider 出现计算失败时(如网络错误导致),Riverpod 不会立刻报错,而是自动尝试重新计算,从而让对短暂性错误有更强的恢复能力
  • 暂停/恢复支持: 当一个 Widget 不在屏幕上时,与之关联的 Provider 监听器现在会自动暂停
  • 离线和变更 (Mutation) 支持 (实验性): Riverpod 3.0 引入了对离线数据缓存和 “mutation” 操作的实验性支持,让处理数据持久化和异步操作(如表单提交)变得更加容易
  • 简化的 API: 通过合并 AutoDisposeNotifierNotifier 等接口,API 变得更加统一和简洁

同时 3.0 也引入了一些破坏性改动:

  • 传统 Provider 的迁移: StateProvider, StateNotifierProviderChangeNotifierProvider 这些在 3.0 属于“传统”API ,它们虽然没有被移除,但都被移至一个新的 legacy 导入路径下,推荐开发者使用新的 Notifier API
  • 统一使用 == 进行更新过滤: 在 3.0 版本所有的 Provider 都使用 == (相等性) 而非 identical 来判断状态是否发生变化,从而决定是否需要重建
  • 简化的 Ref 和移除的子类: Ref 不再有泛型参数,并且像 ProviderRef.stateRef.listenSelf 这样的属性和方法都被移至 Notifier ,同时所有 Ref 的子类(如 FutureProviderRef)都已被移除,现在可以直接使用 Ref
  • 移除 AutoDispose 接口: 自动释放功能被简化,不再需要独立的 AutoDisposeProvider, AutoDisposeNotifier 等接口,现在所有 Provider 都可以是 auto-dispose
  • ProviderObserver 接口变更: ProviderObserver 的方法签名发生了变化,现在传递的是一个 ProviderObserverContext 对象,其中包含了 ProviderContainerProviderBase 等信息

下面我们详细讲解这些变化。

自动重试失败的 Provider

在 Riverpod 3.0 中,Provider 现在默认会自动重试失败的计算,这意味着如果一个 Provider 因为网络波动、服务暂时不可用等瞬时错误而构建失败,它不会立即报错,而是会自动尝试重新计算,直到成功为止

这个功能是默认开启的,我相信你第一想法就是我不需要,在某些情况下你可能希望禁用或自定义重试逻辑:

  • 全局禁用/自定义: 你可以在 ProviderScopeProviderContainer 的顶层进行全局配置,通过设置 retry 参数,可以精细地控制重试逻辑,例如根据错误类型或重试次数来决定是否继续重试,以及重试的间隔时间:

    void main() {runApp(ProviderScope(// 全局禁用自动重试retry: (retryCount, error) => null,child: MyApp(),),);
    }
    
  • 针对特定 Provider 禁用/自定义: 可以在定义单个 Provider 时,通过其 retry 参数进行独立的配置:

    (retry: retry)
    class TodoList extends _$TodoList {// 从不重试这个特定的 providerstatic Duration? retry(int retryCount, Object error) => null;List<Todo> build() => [];
    }
    

暂停/恢复支持

为了优化资源使用,Riverpod 3.0 引入了暂停/恢复机制。当一个 Widget(及其关联的 Provider 监听器)不在屏幕上时,监听器会自动暂停,这个行为是默认启用的,并且看起来不支持全局关闭,你可以通过 Flutter 的 TickerMode 来手动控制监听器的暂停行为:

class MyWidget extends StatelessWidget {Widget build(BuildContext context) {return TickerMode(enabled: false, // 这会暂停监听器child: Consumer(builder: (context, ref, child) {// 这个 "watch" 将会暂停// 直到 TickerMode 设置为 truefinal value = ref.watch(myProvider);return Text(value.toString());},),);}
}

离线和变更 (Mutation) 支持 (实验性)

Riverpod 3.0 引入了两个实验性功能:

  • 离线支持: 允许你轻松地将 Provider 的状态持久化,以便在应用重启或离线时恢复
  • 变更 (Mutation) 支持: 提供了一种结构化的方式来处理异步操作,例如用户登录、提交表单或任何会改变应用状态的动作
// A mutation to track the "add todo" operation.
// The generic type is optional and can be specified to enable the UI to interact
// with the result of the mutation.
final addTodo = Mutation<Todo>();// We listen to the current state of the "addTodo" mutation.// Listening to this will not perform any side effects by itself.final addTodoState = ref.watch(addTodo);switch (addTodoState) {case MutationIdle():// Show a button to add a todocase MutationPending():// Show a loading indicatorcase MutationError():// Show an error messagecase MutationSuccess():// Show the created todo
}

API 变动

Riverpod 3.0 对其核心 API 进行了大幅简化和统一,具体有:

1、合并 AutoDispose 接口

在之前的版本中,有大量带有 AutoDispose 前缀的接口,如 AutoDisposeProviderAutoDisposeNotifier ,而在 3.0 中这些接口被统一了,现在,你只需要使用 ProviderNotifier 等核心接口:

//**V2.0:**
// 使用 .autoDispose 修饰符
final myProvider = Provider.autoDispose((ref) {return MyObject();
});//**V3.0:**
// 1. 对于手写 Provider
final myProvider = Provider((ref) => MyObject(),isAutoDispose: true, // 使用 isAutoDispose 参数
);// 2. 对于代码生成的 Provider
(keepAlive: false) // keepAlive: false 是默认行为,等同于 autoDispose
int myProvider(MyProviderRef ref) {return 0;
}

2、移除 FamilyNotifier 变体

类似于 AutoDispose 的简化,FamilyNotifierFamilyAsyncNotifier 等家族变体也被移除了,现在你只需要使用 NotifierAsyncNotifier 等核心 Notifier,并通过构造函数来传递参数

final provider = NotifierProvider.family<CounterNotifier, int, String>(CounterNotifier.new);-class CounterNotifier extends FamilyNotifier<int, String> {
+class CounterNotifier extends Notifier<int> {
+  CounterNotifier(this.arg);
+  final String arg;@override
-  int build(String arg) {
+  int build() {// 在这里使用 `arg`return 0;}
}

3、 Provider 变动

统一在 Riverpod 3.0 中,StateProvider, StateNotifierProvider, 和 ChangeNotifierProvider 被归类为“传统(legacy)”API,这新的 Notifier API 更加灵活、功能更强大,并且与代码生成(code generation)的结合更紧密,可以显著减少样板代码,现在推荐使用:

  • Notifier: 用于替换 StateNotifierProvider,管理同步状态,它是一个可以被监听的类,并且可以定义自己的公共方法来修改状态。
  • AsyncNotifier: 用于替换处理异步操作的 StateNotifierProviderFutureProvider,它专门用于管理异步状态(如从网络获取数据),并内置了对加载、数据和错误状态的处理
  • StreamNotifier: 用于替代 StreamProvider

V2.0 :

import 'package:flutter_riverpod/legacy.dart'; // 需要使用 legacy 导入// Before:
final valueProvider = FutureProvider<int>((ref) async {ref.listen(anotherProvider, (previous, next) {ref.state++;});ref.listenSelf((previous, next) {print('Log: $previous -> $next');});ref.future.then((value) {print('Future: $value');});return 0;
});

V3.0 (新的 Notifier API):

// After
class Value extends AsyncNotifier<int> {Future<int> build() async {ref.listen(anotherProvider, (previous, next) {ref.state++;});listenSelf((previous, next) {print('Log: $previous -> $next');});future.then((value) {print('Future: $value');});return 0;}
}
final valueProvider = AsyncNotifierProvider<Value, int>(Value.new);

可以看到,如果用的是 2.x 的注解,其实并不需要变动什么。

所以,现在推荐的 API 是 NotifierAsyncNotifier,它们是基于类的 Provider,其原理是将状态的定义 (build 方法)修改状态的方法 封装在同一个类中,目的在于:

  • 逻辑内聚: 与特定状态相关的所有代码都在一个地方,易于管理
  • 代码更简洁: 结合代码生成,你只需要定义一个类,Provider 会被自动创建
  • 类型安全: 你可以定义强类型的公共方法来修改状态,而不是直接暴露状态对象本身

4、统一使用 == 进行更新过滤

这个改动统一了 Provider 的行为:

  • identical: 之前它检查两个引用是否指向同一个内存地址,两个内容完全相同的不同对象,identical 会返回 false
  • == (相等性): 现在检查两个对象是否相等,对于自定义类,你可以重写 == 操作符来定义相等的标准(例如,如果两个 User 对象的 id 相同,则认为它们相等)

具体是,在 V2.0 中某些 Provider(如 Provider)使用 identical 来判断状态是否变化,而另一些则使用 ==,这意味着,即使你提供了一个内容相同但实例不同的新对象,前者也不会通知监听者更新,因为它认为对象“没有变化”。

在 V3.0 中,所有 Provider 都默认使用 == 来比较新旧状态,如果新旧状态通过 == 比较后结果为 true,则不会通知监听者进行重建

举个例子,假设你有一个 User 类,并且你已经重写了 == 操作符:

class User {final String name;User(this.name);bool operator ==(Object other) =>identical(this, other) ||other is User && runtimeType == other.runtimeType && name == other.name;int get hashCode => name.hashCode
}

现在,有一个 Provider 返回 User 对象:

final userProvider = Provider((ref) => User('John'));

在某个操作后,你让这个 Provider 返回了一个新的 User 实例,但 name 属性仍然是 ‘John’:

  • V2.0 (使用 identical): 由于新旧 User 对象是不同的实例(内存地址不同),identical 会返回 false,UI 会重建。
  • V3.0 (使用 ==): 由于我们重写了 ==,只要 name 相同,user1 == user2 就会返回 true。因此,Riverpod 会认为状态没有变化,UI 不会重建,从而避免了不必要的刷新。

另外,如果你需要自定义这种行为,可以在你的 Notifier 中重写 updateShouldNotify 方法。

5、 简化的 Ref 和移除的子类

这个改动的核心目的是简化 AP和提升类型安全,保证API 更统一,因为以前根据 Provider 类型的不同(如 Provider vs FutureProvider),ref 的类型也不同(ProviderRef vs FutureProviderRef),它们各自有不同的属性(例如 FutureProviderRef 有一个 .future 属性),这增加了学习成本,而现在所有 ref 都是同一个 Ref 类型,API 更加一致:

V2.0:

// 使用 .autoDispose 修饰符
final myProvider = Provider.autoDispose((ref) {return MyObject();
});

V3.0:

// 1. 对于手写 Provider
final myProvider = Provider((ref) => MyObject(),isAutoDispose: true, // 使用 isAutoDispose 参数
);// 2. 对于代码生成的 Provider
(keepAlive: false) // keepAlive: false 是默认行为,等同于 autoDispose
int myProvider(MyProviderRef ref) {return 0;
}

类似改动让 API 更加统一,你不需要再记忆两套不同的 Provider 名称,同时职责更清晰,像 ref.stateref.listenSelf 这样的操作,本质上是与状态本身的管理相关的,将这些功能移入 Notifier 类,让 Notifier 成为状态和其业务逻辑的唯一管理者,而 ref 则专注于依赖注入(读取其他 providers)。

例如你需要在一个 Provider 内部监听自身状态的变化来执行某些副作用(比如日志记录):

V2.0:

final myProvider = FutureProvider<int>((ref) {// 使用 ref.listenSelf 监听自身状态变化ref.listenSelf((previous, next) {print('Value changed from $previous to $next');});return Future.value(0);
});

V3.0:


class MyNotifier extends _$MyNotifier {Future<int> build() async {// listenSelf 现在是 Notifier 的一个方法listenSelf((previous, next) {print('Value changed from $previous to $next');});return 0;}
}

可以看到,在 V3.0 中,listenSelf 成为了 MyNotifier 类的一部分,代码的组织结构更加清晰,或者假设你想在一个 Provider 内部,每当其状态更新时,就将新状态持久化到本地存储:

V2.0 (使用 ref.listenSelf):

final counterProvider = FutureProvider<int>((ref) async {// 在 Provider 内部监听自身ref.listenSelf((previous, next) {if (next.hasValue) {SharedPreferences.getInstance().then((prefs) {prefs.setInt('counter', next.value!);});}});// 返回初始值final prefs = await SharedPreferences.getInstance();return prefs.getInt('counter') ?? 0;
});

V3.0 (使用 Notifier.listenSelf):


class Counter extends _$Counter {Future<int> build() async {// listenSelf 现在是 Notifier 的一个方法listenSelf((previous, next) {if (next.hasValue) {SharedPreferences.getInstance().then((prefs) {prefs.setInt('counter', next.value!);});}});final prefs = await SharedPreferences.getInstance();return prefs.getInt('counter') ?? 0;}void increment() async {state = AsyncData((state.value ?? 0) + 1);}
}

可以看到,在 V3.0 中逻辑更加内聚,Counter 类不仅负责创建状态,还负责处理与该状态相关的副作用,代码的可读性和维护性更高。

6、 ProviderObserver 接口变更

ProviderObserver 是一个用于监听应用中所有 Provider 变化的强大工具,常用于日志记录或调试,在 V3.0 中它的接口发生了变化:

以前 ProviderObserver 的方法会接收 providervaluecontainer 等多个独立的参数,现在这些参数被统一封装在一个 ProviderObserverContext 对象。

V2.0:

class MyObserver extends ProviderObserver {void didAddProvider(ProviderBase provider,Object? value,ProviderContainer container,) {print('Provider ${provider.name ?? provider.runtimeType} was created');}
}

V3.0:

class MyObserver extends ProviderObserver {@override
-  void didAddProvider(ProviderBase provider, Object? value, ProviderContainer container) {
+  void didAddProvider(ProviderObserverContext context, Object? value) {
-    print('Provider ${provider.name ?? provider.runtimeType} was created');
+    print('Provider ${context.provider.name ?? context.provider.runtimeType} was created');}
}

最后,注解模式并没有被抛弃,而是得到了进一步加强,如果是在 2.x 版本使用了注解模式,那么你的迁移成本会更低,例如

// Before:

Future<int> value(ValueRef ref) async {ref.listen(anotherProvider, (previous, next) {ref.state++;});ref.listenSelf((previous, next) {print('Log: $previous -> $next');});ref.future.then((value) {print('Future: $value');});return 0;
}// After

class Value extends _$Value {Future<int> build() async {ref.listen(anotherProvider, (previous, next) {ref.state++;});listenSelf((previous, next) {print('Log: $previous -> $next');});future.then((value) {print('Future: $value');});return 0;}
}

整体来看, Riverpod 3.0 的重构主要围绕:

  • 简化 API ,例如移除 AutoDisposeFamily 的各种变体,统一 Ref 的类型,通过更少的、功能更强大的构建块来替代大量专用但零散的 API

  • 提升一致性 ,通过统一内部行为,让对应 Provider 的表现更加可预测,例如统一使用 == 进行更新过滤,确保了无论使用哪种 Provider,对应的重建逻辑都是一致

  • 增强功能 ,在不增加复杂度的前提下,引入如自动重试、离线缓存和 Mutation (变更) 支持

那么,你喜欢 Riverpod 3.0 吗

参考链接

  • https://riverpod.dev/docs/3.0_migration

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

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

相关文章

Xcode 上传 ipa 全流程详解 App Store 上架流程、uni-app 生成 ipa 文件上传与审核指南

对于 iOS 开发者而言&#xff0c;应用开发完成后最重要的一步就是将应用打包为 ipa 文件&#xff0c;并上传至 App Store Connect 进行分发或上架。 其中&#xff0c;Xcode 上传 ipa 是最常见的方法&#xff0c;但很多开发者在实际操作中常常遇到卡住、上传失败或签名错误等问题…

快速选中对象

图片要求 图片背景单纯&#xff0c;对象边缘比较清晰 对象选择工具 选择对象选择工具后&#xff0c;画出大致区域&#xff0c;系统将自动分析图片内容&#xff0c;从而实现快速选择图片中的一个惑多个对象他有两种模式&#xff0c;分别是举行与套索模式。使用时可以先选中对象的…

点到点链路上的OSPF动态路由(2025年9月10日)

一、前言前面我们已经分享过了静态路由、缺省路由、浮动静态路由这些静态路由的配置。接下来将会 陆陆续续开始分享动态路由以及其他路由配置。博主这里是一个新人&#xff0c;了解这些路由配置不是自上而下的&#xff0c;而是自下而上的&#xff0c;也就是说通过实验去理解原理…

技术视界 | 末端执行器:机器人的“手”,如何赋予机器以生命?

在现代自动化系统中&#xff0c;末端执行器&#xff08;End Effector&#xff09;作为机器人与物理世界交互的“手”&#xff0c;发挥着至关重要的作用。它直接安装在机械臂末端&#xff0c;不仅是机器人实现“抓取、感知和操作”三大核心功能的关键部件&#xff0c;更是整个自…

滑动窗口概述

滑动窗口算法简介滑动窗口是一种用于处理数组或字符串子区间问题的高效算法。它通过维护一个动态窗口&#xff08;通常由两个指针表示&#xff09;来避免重复计算&#xff0c;将时间复杂度从O(n)优化到O(n)。基本实现步骤初始化窗口指针&#xff1a;通常使用left和right指针表示…

AI 创建学生管理系统

使用腾讯元宝创建&#xff0c;整体效果不错。修正2个bug跑起来&#xff0c;达到了需要的功能先上效果图&#xff1a;按钮分类别配色&#xff0c;界面清爽。喜欢这布局创建过程&#xff1a;prompt: 使用最新稳定vue版&#xff0c;使用pinia存储&#xff0c;基于typescript, 样式…

ASP.NET Core 中的简单授权

ASP.NET Core 中的授权通过 [Authorize] 属性及其各种参数控制。 在其最基本的形式中&#xff0c;通过向控制器、操作或 [Authorize] Page 应用 Razor 属性&#xff0c;可限制为仅允许经过身份验证的用户访问该组件。 使用 [Authorize] 属性 以下代码限制为仅允许经过身份验证…

leetcode 493 翻转对

一、题目描述 二、解题思路 本题的思路与逆序数的思路相似&#xff0c;采用归并排序的思路来实现。leetcode LCR 170.交易逆序对的总数-CSDN博客 注意&#xff1a;但是逆序数的ret更新在左、右区间合并时更新&#xff0c;但本题ret更新在左、右区间合并前更新。 三、代码实现…

初识微服务-nacos配置中心

配置中心 概述 配置中心是微服务中不可或缺的组件&#xff0c;因为如果没有配置中心&#xff0c;那么各个微服务的的配置信息无法得到统一和管理&#xff0c;会变得冗余。 :::color4 配置中心是用于管理应用程序配置信息的工具 集中管理配置&#xff1a;解决微服务架构下配置分…

Android webview更新记录-aosp

一、下载 webview下载地址&#xff0c;感谢火哥分享&#xff0c;版本很全。 https://www.firepx.com/app/android-system-webview/ 二、更新 external/chromium-webview/prebuilt 具体更新那个目录&#xff0c;需要查看编译架构 这个看你的lunch就行&#xff0c;这里我的是a…

无感FOC(无传感器磁场定向控制)

我们来详细解析无感FOC&#xff08;无传感器磁场定向控制&#xff09;中的高频方波注入&#xff08;High-Frequency Square-Wave Injection, HFSWI&#xff09;​​ 的原理。这是一个用于零低速或极低速范围内估算转子位置的核心技术。核心思想与要解决的问题在电机静止或转速极…

MATLAB基于博弈论组合赋权-云模型的煤与瓦斯突出危险性评价

MATLAB基于博弈论组合赋权-云模型的煤与瓦斯突出危险性评价 1. 问题背景与核心目标 背景&#xff1a;煤与瓦斯突出是煤矿生产中的一种极其复杂的动力灾害&#xff0c;其发生机理复杂&#xff0c;影响因素众多&#xff08;如地应力、瓦斯压力、煤体物理属性等&#xff09;。对其…

JavaWeb-Servlet总结及JSP

目录 一、文件下载 二、ServletConfig对象 三、Web.xml文件使用总结 四、server.xml文件 五、JSP动态网页技术 1.概念&#xff1a; 2.动态网页&#xff1a; 3.特点&#xff1a; 4.JSP的访问原理&#xff1a; 5.JSP的文档说明&#xff1a; 6.jsp实际运行文件&#xff…

DDIM和DDPM之 间的区别与联系

核心关系概述 首先&#xff0c;要理解DDIM并不是一个全新的模型&#xff0c;而是DDPM的一个精巧的重新参数化和扩展。它们使用完全相同的训练目标和方法&#xff0c;因此你可以用一个训练好的DDPM模型直接来运行DDIM的采样算法&#xff0c;而无需重新训练。 DDIM的核心贡献是&a…

c++---map和set

这里再提二叉树&#xff08;二叉搜索树&#xff09;&#xff0c;是为了后面讲解map和set做准备。 一、二叉搜索树 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树。 若它的左子树不为空&#xff0c;则左子树上所有节点的值都…

windows下,podman迁移镜像文件位置

docker-desktop有自带的镜像文件位置迁移功能&#xff0c;但podman-desktop还没有&#xff0c;所以只能自己操作wsl导入导出来实现# 1.一定要先停止当前machine podman machine stop# 2. 导出当前 machine&#xff08;会生成 tar 镜像&#xff09; wsl --export podman-machine…

Champ-基于3D的人物图像到动画视频生成框架

本文转载自&#xff1a;https://www.hello123.com/champ ** 一、&#x1f916; Champ 是什么&#xff1f; 阿里 南大 复旦联手打造的虚拟人动作黑科技&#xff01;Champ 可不是普通动画工具&#xff0c;它能把你随手拍的小视频变成专业级 3D 动画 —— 无论跳舞、打拳还是走…

Thingsboard 3.4 源码运行 Mac Mini

拉取源码 git clone https://github.com/thingsboard/thingsboard.gitjdk11 java -version java version "11.0.27" 2025-04-15 LTS Java(TM) SE Runtime Environment 18.9 (build 11.0.278-LTS-232) Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.278-LTS-23…

【AI大模型面试宝典60题】1-5

目录 Q1:仅编码器(BERT 类)、仅解码器(GPT 类)和完整的编码器-解码器架构各有什么优缺点? 1. 编码器架构 (Encoder-only) - 代表:BERT系列 2. 解码器架构 (Decoder-only) - 代表:GPT系列 3. 编码器-解码器架构 (Encoder-Decoder) - 代表:T5、BART 升华与总结 (总…

macOS中找不到钥匙串访问

如果在macOS中找不到钥匙串访问&#xff0c;请操作如下命令&#xff1a; security list-keychains可以看到类似&#xff1a; “/Library/Keychains/System.keychain” 然后执行&#xff1a; open /Library/Keychains/System.keychain然后可以将应用保留在程序坞中保留。