🎢

StreamNotifierを使ってみた

2023/12/18に公開

Overview

riverpod2.0には、StreamNotifierなるものがあるらしい?
AsyncNotifierだと、FutureOrにデータ型がなるから、Streamのデータを返せない?
ということは、こちらを使う必要がありそう...

https://pub.dev/documentation/riverpod/latest/riverpod/StreamNotifier-class.html

ストリームを作成するビルドを備えた AsyncNotifier のバリアント。

これは、時間の経過とともに値を変更できる StreamProvider と考えることができます。

このプロバイダーを使用するための構文は、プロバイダーの関数が「ref」を受け取らない (ファミリーの場合は引数も受け取らない) という点で、他のプロバイダーとは少し異なります。 代わりに、関連する AsyncNotifier で参照 (および引数) に直接アクセスできます。

これは、時間の経過とともに値を変更できる StreamProvider と考えることができます。 autoDispose またはファミリーを使用すると、通知タイプが変わります。 StreamNotifier を拡張する代わりに、次のいずれかを拡張する必要があります。

  • AutoDisposeStreamNotifier for autoDispose
  • FamilyStreamNotifier for family
  • AutoDisposeFamilyStreamNotifier for autoDispose.family

summary

とりあえずやってみよう!
ViewModelとは言えないが、Streamを返すNotifierを作ってみる。やることは単縦で、StreamでFirestoreのデータを全て取得して、リアルタイムに反映してもらう。

StreamNotifier
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'stream_view_model.g.dart';


class StreamViewModel extends _$StreamViewModel {
  
  Stream<QuerySnapshot<Map<String, dynamic>>> build() {
    return getStream();
  }

  Stream<QuerySnapshot<Map<String, dynamic>>> getStream(){
    final snapshot = FirebaseFirestore.instance.collection('user').snapshots();
    return snapshot;
  }
}

View側には、いつも通りの書き方で表示します。

View

データを全て表示

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isige_app/stream_view_model.dart';

class StreamView extends ConsumerWidget {
  const StreamView({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // AsyncValue<QuerySnapshot<Map<String, dynamic>>>でデータが返ってくる
    final streamValue = ref.watch(streamViewModelProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('StreamView'),
      ),
      body: Center(
        child: streamValue.when(
          data: (data) {
            return ListView.builder(
              itemCount: data.docs.length,
              itemBuilder: (context, index) {
                final docData = data.docs[index].data();
                final name = docData['name'];
                final email = docData['email'];
                return ListTile(
                  title: Text(name),
                  subtitle: Text(email),
                );
              },
            );
          },
          loading: () => const CircularProgressIndicator(),
          error: (error, stackTrace) => Text(error.toString()),
        ),
      ),
    );
  }
}

main.dartでimportして実行する

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isige_app/firebase_options.dart';
import 'package:isige_app/stream_view.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: StreamView(),
    );
  }
}

データの取得に成功すればこんな感じで表示できます:

thoughts

今回は、StreamNotifierなるものを使って見ました。今の所使っている人たちは見かけないような??
riverpod3.0からは、whenではなくてswitch式で、コードを書くらしく試してみました。時々使うんですけど、失敗することがあって何が原因かはまだわからないですね。今回は成功してるポイです。

おまけ:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isige_app/stream_view_model.dart';

class StreamView extends ConsumerWidget {
  const StreamView({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.green,
        title: const Text('Switch Stream'),
      ),
      body: const UserStream());
  }}


class UserStream extends ConsumerWidget {
  const UserStream({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final streamValue = ref.watch(streamViewModelProvider);
    switch (streamValue) {
      case AsyncData(:final value):
        final data = value;
        return ListView.builder(
          itemCount: data.docs.length,
          itemBuilder: (context, index) {
            final docData = data.docs[index].data();
            final name = docData['name'];
            final email = docData['email'];
            return ListTile(
              title: Text(name),
              subtitle: Text(email),
            );
          },
        );
      case AsyncError(:final error):
        // エラーメッセージを表示するなど、エラー時の処理を書く
        return Text('An error occurred: $error');
      default:
        // データがロード中の場合や、その他のケースで表示するWidgetを返す
        return const CircularProgressIndicator();
    }
  }
}

ちゃんと表示できてるでしよう:

Discussion