跳到主要內容

Websocket 和同步執行

到目前為止,我們只介紹瞭如何建立一個 Future
這是有意為之的,因為 Future 是 Riverpod 應用程式構建方式的核心。 但是,如有必要,Riverpod 還支援其他格式。

特別是,除了 Future 型別的提供者程式,還存在靈活的型別:

  • 同步返回一個物件,例如建立“儲存庫”。
  • 返回一個 Stream,例如監聽 websockets。

返回 Future 和返回 Stream 或 object 總體上非常相似。 本頁將解釋這些用例的細微差別和各種提示。

同步返回物件

若要同步建立物件,請確保提供者程式不返回 Future:


int synchronousExample(Ref ref) {
return 0;
}

當提供者程式同步建立物件時,這會影響物件的使用方式。 具體而言,同步值不會被包裝在“AsyncValue”中:

  Consumer(
builder: (context, ref, child) {
// The value is not wrapped in an "AsyncValue"
int value = ref.watch(synchronousExampleProvider);

return Text('$value');
},
);

這種差異的後果是,如果提供者程式丟擲異常,嘗試讀取該值會重新丟擲錯誤。 或者,當使用 ref.listen 時,將呼叫 “onError” 回撥。

可監聽物件的注意事項

可監聽物件,例如 ChangeNotifierStateNotifier 是不受支援的。
如果出於相容性原因,您需要與其中一個物件進行互動, 一種解決方法是將其通知機制透過管道傳遞給 Riverpod。

/// A provider which creates a ValueNotifier and update its listeners
/// whenever the value changes.

ValueNotifier<int> myListenable(Ref ref) {
final notifier = ValueNotifier(0);

// Dispose of the notifier when the provider is destroyed
ref.onDispose(notifier.dispose);

// Notify listeners of this provider whenever the ValueNotifier updates.
notifier.addListener(ref.notifyListeners);

return notifier;
}
資訊

如果你多次需要這樣的邏輯,值得注意的是, 邏輯是共享的!"ref" 物件被設計為可組合的。 這樣就可以從提供者程式中提取處置/監聽邏輯:

extension on Ref {
// We can move the previous logic to a Ref extension.
// This enables reusing the logic between providers
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
onDispose(notifier.dispose);
notifier.addListener(notifyListeners);
// We return the notifier to ease the usage a bit
return notifier;
}
}


ValueNotifier<int> myListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(0));
}


ValueNotifier<int> anotherListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(42));
}

監聽一個流

現代應用程式的一個常見用例是與 Websocket 互動, 例如與 Firebase 或 GraphQL 訂閱進行互動。
與這些 API 的互動通常是透過監聽一個 Stream

為了幫助實現這一點,Riverpod 自然地支援 Stream 物件。 與 Future 一樣,該物件將轉換為 AsyncValue


Stream<int> streamExample(Ref ref) async* {
// Every 1 second, yield a number from 0 to 41.
// This could be replaced with a Stream from Firestore or GraphQL or anything else.
for (var i = 0; i < 42; i++) {
yield i;
await Future<void>.delayed(const Duration(seconds: 1));
}
}

class Consumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
// The stream is listened to and converted to an AsyncValue.
AsyncValue<int> value = ref.watch(streamExampleProvider);

// We can use the AsyncValue to handle loading/error states and show the data.
return switch (value) {
AsyncValue(:final error?) => Text('Error: $error'),
AsyncValue(:final valueOrNull?) => Text('$valueOrNull'),
_ => const CircularProgressIndicator(),
};
}
}
資訊

Riverpod 不知道自定義 Stream 實現,例如 RX 的 BehaviorSubject。 因此,返回 BehaviorSubject 不會同步向小部件公開,value 即使在建立時已經可用。

停用從 Stream / Future 轉換到 AsyncValue

預設情況下,Riverpod 會將 StreamFuture 轉換為 AsyncValue。 儘管很少需要,但可以透過將返回型別包裝在 Raw 泛型中來停用此行為。

警告

通常不建議停用轉換 AsyncValue。 只有當您知道自己在做什麼時才這樣做。


Raw<Stream<int>> rawStream(Ref ref) {
// "Raw" is a typedef. No need to wrap the return
// value in a "Raw" constructor.
return const Stream<int>.empty();
}

class Consumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
// The value is no-longer converted to AsyncValue,
// and the created stream is returned as is.
Stream<int> stream = ref.watch(rawStreamProvider);
return StreamBuilder<int>(
stream: stream,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}