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
英文地址,請注明出處:http://en.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然后可以將應用保留在程序塢中保留。