メインコンテンツに進む

Websocketsと同期実行

これまで Futureの作成方法についてのみ説明しました。
これは意図的なものであり、FutureRiverpod アプリケーションの基盤となるべきだからです。
しかし、必要に応じて 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 オブジェクトに関する考慮事項

ChangeNotifierStateNotifierなどの 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 はRXBehaviorSubjectなどのカスタム Stream 実装を認識していません。
そのため、BehaviorSubject を返しても、作成時に既に利用可能な場合でも、その value がウィジェットに同期的に公開されることはありません。

StreamFutureAsyncValueへの変換を無効にする

デフォルトでは、Riverpod はStreamFutureAsyncValueに変換します。
これを無効にすることができますが、これはまれに必要な場合のみです。
無効にするには、戻り値の型を 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}');
},
);
}
}