Websocketsと同期実行
これまで Future
の作成方法についてのみ説明しました。
これは意図的なものであり、Future
は Riverpod
アプリケーションの基盤となるべきだからです。
しかし、必要に応じて Riverpod は他の形式もサポートしています。
特に、Future
の代わりに、provider は以下のことができます:
- 同期的にオブジェクトを返すこと(例:"Repository"の作成)。
Stream
を返すこと(例:websockets の受信)。
Future
を返すことと、Stream
やオブジェクトを返すことは全体的に非常に似ています。
このページでは、それらのケースにおける微妙な違いやさまざまなヒントを説明します。
同期的にオブジェクトを返すこと
オブジェクトを同期的に作成するには、provider が Future を返さないことを確認してください:
int synchronousExample(SynchronousExampleRef ref) {
return 0;
}
provider が同期的にオブジェクトを作成する時、オブジェクトの消費(consumed)方法に影響を与えます。
特に、同期的な値は”AsyncValue”でラップされません:
Consumer(
builder: (context, ref, child) {
// 値が "AsyncValue" でラップされていません。
int value = ref.watch(synchronousExampleProvider);
return Text('$value');
},
);
この違いの結果として、provider がエラーをスローした場合、値を読み取ろうとするとエラーが再スローされます。
代わりに、ref.listen
を使用すると、“onError”コールバックが呼び出されます。
Listenable オブジェクトに関する考慮事項
ChangeNotifier
や StateNotifier
などの Listenable オブジェクトはサポートされていません。
もし、互換性のためにこれらのオブジェクトのいずれかとやり取りする必要がある場合、その通知メカニズムを Riverpod にパイプ(pipe)することが 1 つの回避策です。
/// 値が変更されるたびにValueNotifierを生成してリスナーを更新するproviderです。
ValueNotifier<int> myListenable(MyListenableRef ref) {
final notifier = ValueNotifier(0);
// providerが破棄された時にnotifierを破棄します。
ref.onDispose(notifier.dispose);
// ValueNotifierが更新されるたびにproviderのリスナーに通知します。
notifier.addListener(ref.notifyListeners);
return notifier;
}
このようなロジックが何度も必要な場合、ロジックを共有する価値があります。
“ref”オブジェクトはコンポーザブルに設計されています。
これにより、provider の外で dispose/listening ロジックを抽出することができます:
extension on Ref {
// 前述のロジックをRefの拡張に移動することができます。
// これにより、provider間でロジックを再利用できるようになります。
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
onDispose(notifier.dispose);
notifier.addListener(notifyListeners);
// 使用を少し簡単にするために、notifierを返します。
return notifier;
}
}
ValueNotifier<int> myListenable(MyListenableRef ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(0));
}
ValueNotifier<int> anotherListenable(AnotherListenableRef ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(42));
}
Stream のリッスン
現代のアプリケーションの一般的なユースケースは、Firebase や GraphQL サブスクリプションなどの Websockets とやり取りすることです。
これらの API とやり取りする場合、多くの場合、Stream をリッスンします。
そのために、Riverpod は自然にStream
オブジェクトをサポートします。
Future
と同様に、オブジェクトは AsyncValue に変換されます
Stream<int> streamExample(StreamExampleRef ref) async* {
// 1秒ごとに0から41までの数字を返します。
// これはFirestoreやGraphQL等のStreamに置き換えることが可能です。
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) {
// streamがリッスンされ、AsyncValueに変換されます。
AsyncValue<int> value = ref.watch(streamExampleProvider);
// AsyncValueを使ってロード/エラー状態を処理し、データを表示することができます。
return switch (value) {
AsyncValue(:final error?) => Text('Error: $error'),
AsyncValue(:final valueOrNull?) => Text('$valueOrNull'),
_ => const CircularProgressIndicator(),
};
}
}
Riverpod はRX
のBehaviorSubject
などのカスタム Stream 実装を認識していません。
そのため、BehaviorSubject を返しても、作成時に既に利用可能な場合でも、その value がウィジェットに同期的に公開されることはありません。
Stream
やFuture
のAsyncValue
への変換を無効にする
デフォルトでは、Riverpod はStream
やFuture
をAsyncValue
に変換します。
これを無効にすることができますが、これはまれに必要な場合のみです。
無効にするには、戻り値の型を Raw
typedef でラップします。
AsyncValue
変換を無効にすることは一般的には推奨されません。
これを行うのは、確実に必要である場合のみにしてください。
Raw<Stream<int>> rawStream(RawStreamRef ref) {
// "Raw"はtypedefです。
// 戻り値を"Raw"コンストラクタでラップする必要はありません。
return const Stream<int>.empty();
}
class Consumer extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// 値はAsyncValueに変換されず、
// 作成されたstreamはそのまま返されます。
Stream<int> stream = ref.watch(rawStreamProvider);
return StreamBuilder<int>(
stream: stream,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}