🌪️

RiverPod - StreamProvider

2022/11/07に公開

はじめに

本記事では、RiverPodに付随しているStreamProvider / StreamControllerについて記載していく

StreamProviderとは

  • 非同期操作が可能なProvider
  • FutureProviderStream
  • StreamFutureのどちらも値を流すことが可能

例1

final streamProvider =
    StreamProvider.autoDispose<int>((ref) => Stream<int>.value(0));

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(streamProvider);

    return MaterialApp(
      home: Scaffold(
        body: value.when(
            data: (data) => Center(child: Text('$data番')),
            error: (_, e) => const SizedBox.shrink(),
            loading: () => const CircularProgressIndicator()),
      ),
    );
  }
}

上記コードでは、単純なStream<int>.valueを用いて、intの0を返却するStreamProviderを定義

例2

final streamProvider =
    StreamProvider.autoDispose<int>((ref) => Stream<int>.value(0));

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final stream = ref.watch(streamProvider.stream);
    final index = useFuture(stream.first);
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('${index.data ?? 0}番'),
        ),
      ),
    );
  }
}

上記ではstreamProviderstream自体を監視対象とし、useFutureで非同期的に'stream.first'の値を取り出すということを実施しています。
※一応以下のようにstreamの部分をfutureにしても正常に値は取得できます。

    final stream = ref.watch(streamProvider.future);
    final index = useFuture(stream);

StreamControllerとは

  • データ/エラー/完了イベントを送信できるストリーム
  • 非同期的にデータを監視 / 送信できる

例1

final streamController = StreamController<int>();

final streamProvider =
    StreamProvider.autoDispose<int>((ref) => streamController.stream);

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final stream = ref.watch(streamProvider.stream);
    final index = useFuture(stream.first);

    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            children: [
              Text('${index.data ?? 0}番'),
              const SecondApp(),
            ],
          ),
        ),
      ),
    );
  }
}

class SecondApp extends StatelessWidget {
  const SecondApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => streamController.sink.add(1),
      child: const Text('tap'),
    );
  }
}

上記では、StreamProviderと併用して、StreamControllerstreamを監視対象としています。
secondApp側でボタン押下時にStreamControlleradd(1)を流しています。
※また、StreamControllerにはstream.listenhasListenneraddStreamなどのメソッドも用意されています。

listenとhasListennerの場合
StreamController<int> streamController = StreamController<int>();

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    useEffect(() {
      if (streamController.hasListener) {
        streamController = StreamController<int>();
      }
      streamController.stream.listen((index) {
        print('$index番');
      });
      return null;
    }, []);
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: const SecondApp(),
        ),
      ),
    );
  }
}

class SecondApp extends StatelessWidget {
  const SecondApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => streamController.sink.add(1),
      child: const Text('tap'),
    );
  }
}

useEffect内で、streamControllerがサブスクライブされているかを確認し、されていない場合はインスタンスを生成し、listenで監視を実施。
ボタンタップにより、sink.addにて特定の値をlisten側に送信している。
addStreamaddStream版と認識していれば問題ない

streamController.sink.addStream(Stream<int>.value(1))

最後に

今回はStreamProvider/StreamControllerにスポットを当てたが
上記以外にも応用できる方法などがあるため、常に模索していきたいと思う。
次回はStateProvider/StateControllerにもスポットを当てていきたいと思う。

Discussion