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” 回调。
可监听对象的注意事项
可监听对象,例如 ChangeNotifier
或 StateNotifier
是不受支持的。
如果出于兼容性原因,您需要与其中一个对象进行交互,
一种解决方法是将其通知机制通过管道传递给 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 会将 Stream
和 Future
转换为 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}');
},
);
}
}