🌪️
RiverPod - StreamProvider
はじめに
本記事では、RiverPodに付随しているStreamProvider
/ StreamController
について記載していく
StreamProviderとは
- 非同期操作が可能な
Provider
-
FutureProvider
のStream
版 -
Stream
とFuture
のどちらも値を流すことが可能
例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}番'),
),
),
);
}
}
上記ではstreamProvider
のstream
自体を監視対象とし、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
と併用して、StreamController
のstream
を監視対象としています。
secondApp
側でボタン押下時にStreamController
にadd(1)
を流しています。
※また、StreamController
にはstream.listen
、hasListenner
、addStream
などのメソッドも用意されています。
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
側に送信している。
※addStream
はadd
のStream
版と認識していれば問題ない
streamController.sink.addStream(Stream<int>.value(1))
最後に
今回はStreamProvider
/StreamController
にスポットを当てたが
上記以外にも応用できる方法などがあるため、常に模索していきたいと思う。
次回はStateProvider
/StateController
にもスポットを当てていきたいと思う。
Discussion