在之前的 《註解模式下的 Riverpod 有什麼特別之處》 我們聊過 Riverpod 2.x 的設計和使用原理,同時當時我們就聊到作者已經在開始探索 3.0 的重構方式,而現在隨著 Riverpod 3.0 的發布,riverpod 帶來了許多細節性的變化。
當然,這也帶來了使用方式上的變動。
廢話不多說,首先 Riverpod 3.0 與 2.0 的對比,新增的功能有:
AutoDisposeNotifier 和 Notifier 等介面,API 變得更加統一和簡潔同時 3.0 也引入了一些破壞性改動:
StateProvider, StateNotifierProvider 和 ChangeNotifierProvider 這些在 3.0 屬於“傳統”API,它們雖然沒有被移除,但都被移至一個新的 legacy 導入路徑下,推薦開發者使用新的 Notifier API== 進行更新過濾: 在 3.0 版本所有的 Provider 都使用 == (相等性) 而非 identical 來判斷狀態是否發生變化,從而決定是否需要重建Ref 和移除的子類: Ref 不再有泛型參數,並且像 ProviderRef.state 和 Ref.listenSelf 這樣的屬性和方法都被移至 Notifier,同時所有 Ref 的子類(如 FutureProviderRef)都已被移除,現在可以直接使用 RefAutoDispose 介面: 自動釋放功能被簡化,不再需要獨立的 AutoDisposeProvider, AutoDisposeNotifier 等介面,現在所有 Provider 都可以是 auto-disposeProviderObserver 介面變更: ProviderObserver 的方法簽名發生了變化,現在傳遞的是一個 ProviderObserverContext 對象,其中包含了 ProviderContainer 和 ProviderBase 等信息接下來我們詳細講解這些變化。
在 Riverpod 3.0 中,Provider 現在預設會自動重試失敗的計算,這意味著如果一個 Provider 因為網路波動、服務暫時不可用等瞬時錯誤而構建失敗,它不会立即报错,而是會自動嘗試重新計算,直到成功為止。
這個功能是預設開啟的,我相信你第一想法就是我不需要,在某些情況下你可能希望禁用或自定義重試邏輯:
ProviderScope 或 ProviderContainer 的頂層進行全局配置,透過設置 retry 參數,可以精細地控制重試邏輯,例如根據錯誤類型或重試次數來決定是否繼續重試,以及重試的間隔時間:void main() {
runApp(
ProviderScope(
// 全局禁用自動重試
retry: (retryCount, error) => null,
child: MyApp(),
),
);
}
retry 參數進行獨立的配置:@Riverpod(retry: retry)
class TodoList extends _TodoList {
// 從不重試這個特定的 provider
static Duration? retry(int retryCount, Object error) => null;
@override
List<Todo> build() => [];
}
為了優化資源使用,Riverpod 3.0 引入了暫停/恢復機制。當一個 Widget(及其關聯的 Provider 監聽器)不在螢幕上時,監聽器會自動暫停,這個行為是預設啟用的,並且看起來不支持全局關閉,你可以透過 Flutter 的 TickerMode 來手動控制監聽器的暫停行為:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TickerMode(
enabled: false, // 這會暫停監聽器
child: Consumer(
builder: (context, ref, child) {
// 這個 "watch" 將會暫停
// 直到 TickerMode 設置為 true
final value = ref.watch(myProvider);
return Text(value.toString());
},
),
);
}
}

Riverpod 3.0 引入了兩個實驗性功能:
// 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 todo
case MutationPending():
// Show a loading indicator
case MutationError():
// Show an error message
case MutationSuccess():
// Show the created todo
}
Riverpod 3.0 對其核心 API 進行了大幅簡化和統一,具體有:
在之前的版本中,有大量帶有 AutoDispose 前綴的介面,如 AutoDisposeProvider、AutoDisposeNotifier,而在 3.0 中這些介面被統一了,現在,你只需要使用 Provider、Notifier 等核心介面:
//**V2.0:**
// 使用 .autoDispose 修飾符
final myProvider = Provider.autoDispose((ref) {
return MyObject();
});
//**V3.0:**
// 1. 對於手寫 Provider
final myProvider = Provider(
(ref) => MyObject(),
isAutoDispose: true, // 使用 isAutoDispose 參數
);
// 2. 對於程式碼生成的 Provider
@Riverpod(keepAlive: false) // keepAlive: false 是預設行為,等同於 autoDispose
int myProvider(MyProviderRef ref) {
return 0;
}
類似於 AutoDispose 的簡化,FamilyNotifier、FamilyAsyncNotifier 等家族變體也被移除了,現在你只需要使用 Notifier、AsyncNotifier 等核心 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;
}
}
統一在 Riverpod 3.0 中,StateProvider, StateNotifierProvider, 和 ChangeNotifierProvider 被歸類為“傳統(legacy)”API,這新的 Notifier API 更加靈活、功能更強大,並且與程式碼生成(code generation)的結合更緊密,可以顯著減少樣板程式碼,現在推薦使用:
Notifier: 用於替代 StateNotifierProvider,管理同步狀態,它是個可以被監聽的類,並且可以定義自己的公共方法來修改狀態。AsyncNotifier: 用於替換處理異步操作的 StateNotifierProvider 或 FutureProvider,它專門用於管理異步狀態(如從網路獲取資料),並內置了對加載、資料和錯誤狀態的處理StreamNotifier: 用於替代 StreamProviderV2.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> {
@override
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 是 Notifier 和 AsyncNotifier,它們是基於類的 Provider,其原理是將狀態的定義 (build 方法)和修改狀態的方法封裝在同一個類中,目的在於:
這個改動統一了 Provider 的行為:
identical: 之前它檢查兩個引用是否指向同一個記憶體地址,兩個內容完全相同的不同物件,identical 會返回 false== (相等性): 現在檢查兩個物件是否相等,對於自定義類,你可以重寫 == 操作符來定義相等的標準(例如,如果兩個 User 物件的 id 相同,則認為它們相等)具體是,在 V2.0 中某些 Provider(如 Provider)使用 identical 來判斷狀態是否變化,而另一些則使用 ==,這意味著,即使你提供了一個內容相同但實例不同的新物件,前者也不會通知監聽者更新,因為它認為物件“沒有變化”。
在 V3.0 中,所有 Provider 都預設使用 == 來比較新舊狀態,如果新舊狀態透過 == 比較後結果為 true,則不會通知監聽者進行重建
舉個例子,假設你有一個 User 類,並且你已經重寫了 == 操作符:
class User {
final String name;
User(this.name);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is User && runtimeType == other.runtimeType && name == other.name;
@override
int get hashCode => name.hashCode;
}
現在,有一個 Provider 返回 User 物件:
final userProvider = Provider((ref) => User('John'));
在某個操作後,你讓這個 Provider 返回了一個新的 User 實例,但 name 屬性仍然是 'John':
identical): 由於新舊 User 物件是不同的實例(記憶體地址不同),identical 會返回 false,UI 會重建。==): 由於我們重寫了 ==,只要 name 相同,user1 == user2 就會返回 true。因此,Riverpod 會認為狀態沒有變化,UI 不會重建,從而避免了不必要的刷新。另外,如果你需要自定義這種行為,可以在你的
Notifier中重寫updateShouldNotify方法。
這個改動的核心目的是簡化 API 和提升類型安全,保證 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
@Riverpod(keepAlive: false) // keepAlive: false 是預設行為,等同於 autoDispose
int myProvider(MyProviderRef ref) {
return 0;
}
類似改動讓 API 更加統一,你不需要再記憶兩套不同的 Provider 名稱,同時職責更清晰,像
ref.state或ref.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:
@riverpod
class MyNotifier extends _MyNotifier {
@override
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):
@riverpod
class Counter extends _Counter {
@override
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 類不僅負責創建狀態,還負責處理與該狀態相關的副作用,程式碼的可讀性和維護性更高。
ProviderObserver 是一個用於監聽應用中所有 Provider 變化的強大工具,常用於日誌記錄或除錯,在 V3.0 中它的介面發生了變化:
以前
ProviderObserver的方法會接收provider、value和container等多個獨立的參數,現在這些參數被統一封裝在一個ProviderObserverContext對象。
V2.0:
class MyObserver extends ProviderObserver {
@override
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(ProviderObserverContext context, Object? value) {
print('Provider ${context.provider.name ?? context.provider.runtimeType} was created');
}
}
最後,註解模式並沒有被拋棄,而是得到了進一步加強,如果是在 2.x 版本使用了註解模式,那麼你的遷移成本會更低,例如:
// Before:
@riverpod
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
@riverpod
class Value extends _Value {
@override
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 的重構主要圍繞:
AutoDispose 和 Family 的各種變體,統一 Ref 的類型,透過更少的、功能更強大的構建塊來替代大量專用但零散的 API== 進行更新過濾,確保了無論使用哪種 Provider,對應的重建邏輯都是一致那麼,你喜歡 Riverpod 3.0 嗎?