Chapter 10

[v0.14.0以下版] StreamProviderでStreamを購読する

村松龍之介
村松龍之介
2023.02.12に更新

StreamProvider

Streamを作成し、最新のイベントを公開するProvider。

作成された値がFutureではなくStreamである点を除いて、FutureProviderと同じように使用できます。

例えば、 Firebase Cloud Firestore で取得できる snapshot は Streamなのでこちらを使用することになります。

Firebase Cloud Firestore での使用例

Firestoreでコレクションやドキュメントのスナップショットを得ると、Streamとして取得できます。

一例として、FirestoreからSnapshot(Stream)を取得する記述例です。

// `items`コレクションのスナップショットを取得
final snapshots = FirebaseFirestore.instance
    .collection('items')
    .snapshots(); // `.get()` だとStreamではなく、Futureになります。

Providerの宣言

StreamProviderを使用して、Firestoreから取得したStreamを返すには以下のように書きます。

Firestoreから取得したデータの型が Map<String, dynamic> なので、
以下の例では、Itemクラスの fromJson コンストラクタを使用する想定です。

final itemsStreamProvider = StreamProvider<List<Item>>((ref) {
  // users/{user.uid} ドキュメントのSnapshotを取得
  final collection = FirebaseFirestore.instance.collection('items');
  // データ(Map型)を取得
  final stream = collection.snapshots().map(
        // CollectionのデータからItemクラスを生成する
        (e) => e.docs.map((e) => Item.fromJson(e.data())).toList(),
      );
  return stream;
});

Widgetからの利用

Widgetからの利用方法は FutureProvider と同じです。
Futureでは、結果としてデータを受け取っておしまいですが、Streamでは、断続的に更新された最新の値を受け取ることができます。


Widget build(BuildContext context, ScopedReader watch) {
  // StreamProviderを読み取る(取得できる型は `AsyncValue<T>`)
  final items = watch(itemsStreamProvider);

  return Scaffold(
    // AsyncValue は `.when` を使ってハンドリングする
    body: items.when(
      // 処理中は `loading` で指定したWidgetが表示される
      loading: () => const CircularProgressIndicator(),
      // エラーが発生した場合に表示されるWidgetを指定
      error: (error, stack) => Text('Error: $error'),
      // 取得した `items` が `data` で使用できる
      data: (items) {
        return ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            final item = items[index];
            return ListTile(title: Text(item.name));
          }
        )
      }
    ),
  );
}

FutureProviderと同じく、 loading error 状態時のハンドリングが不要の場合は、
data?.value を使って簡略化することができます。


Widget build(BuildContext context, ScopedReader watch) {
  // FutureProviderを読み取る(取得できる型は `AsyncValue<T>?`)
  final items = watch(itemsStreamProvider).data?.value;
    // Nullチェックは必要
  if (items == null) {
    return const SizedBox();
  }
  return Scaffold(
    body: Text(items.first.name);
  );
}

参考リンク

StreamProvider | Riverpod公式ドキュメントページ
https://pub.dev/documentation/riverpod/latest/riverpod/StreamProvider-class.html