【Flutter Widget of the Week #14】StreamBuilderを使ってみた
はじめに
Flutter Widget of the Week #14 StreamBuilder についてまとめましたので、紹介します。
StreamBuilder とは
最近のアプリは非同期性が高く、様々なことが時と順序を選ばずに起こり得ます。
今この瞬間にも誰かが文章を投稿しているかもしれないし、いいねを押したかもしれません。
こうしたいつ、どこで、どんなイベントが発生するか分からないイベントを Stream (データの流れ)と考えることができます。
Flutter では Stream を扱う場合に今回紹介する StreamBuilder を使用します。
それでは、実際に使ってみましょう。
StreamBuilder サンプル
まずは API Document にあるサンプルを動かしてみましょう。
import 'dart:async'; // ← StreamController を使うために import する必要あり
class StreamBuilderSample extends StatefulWidget {
const StreamBuilderSample({super.key});
State<StreamBuilderSample> createState() => _StreamBuilderSampleState();
}
class _StreamBuilderSampleState extends State<StreamBuilderSample> {
// StreamBuilder に渡す stream を作成
final Stream<int> _bids = (() {
late final StreamController<int> controller;
controller = StreamController<int>(
onListen: () async {
await Future<void>.delayed(const Duration(seconds: 1));
controller.add(1);
await Future<void>.delayed(const Duration(seconds: 1));
await controller.close();
},
);
return controller.stream;
})();
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: Container(
alignment: FractionalOffset.center,
color: Colors.white,
child: StreamBuilder<int>(
stream: _bids,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
List<Widget> children;
// Error が発生したとき
if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text('Stack trace: ${snapshot.stackTrace}'),
),
];
} else {
switch (snapshot.connectionState) {
// 初期時(今回はすぐ waiting に移るので表示されているようには見えない)
case ConnectionState.none:
children = const <Widget>[
Icon(
Icons.info,
color: Colors.blue,
size: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Select a lot'),
),
];
break;
// 接続中の時(waiting が終わったら非同期処理が開始される)
case ConnectionState.waiting:
children = const <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting bids...'),
),
];
break;
// 非同期処理実行中の時
//snapshot から取得したデータをもとに画面を作成
case ConnectionState.active:
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('\$${snapshot.data}'),
),
];
break;
// 非同期処理が終わった時
// 最後に取得したデータをもとに画面を作成
case ConnectionState.done:
children = <Widget>[
const Icon(
Icons.info,
color: Colors.blue,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('\$${snapshot.data} (closed)'),
),
];
break;
}
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
);
},
),
),
);
}
}
使い方として、
StreamBuilder に stream を渡してデータを流すイベントを設定します。
そして builder の中の snapshot からデータを取得し、そのデータをもとに画面を構成します。
StreamBuilder のプロパティには initialData があり、initialData には最初の snapshot を取得できるまでの間に使う値を指定します。
initialData を使わなくても、 snapshot.hasData でデータが取得できたかを判別し、false/true によって表示する画面を設定することもできます。
snapshot.hasError を使えばエラー時の処理を作ることができます。
他にも非同期処理の状況を確認する方法に snapshot.connectionState を使うこともできます。
connectionState は上記のサンプルコードでも使われているので参考にしてみてください。
connectionState の種類
connectionState enum には以下の4種類があります。
① none
初期時/接続してない時
② waiting
非同期接続未完了 == 接続中
よくこの状態のときにローディング中の画面を表示している
③ active
非同期処理で値が移り変わっている時
④ done
非同期処理がすべて完了した時
StreamBuilder のプロパティについて
StreamBuilder のプロパティは上記で記載していますが改めて紹介します。
(new) StreamBuilder<int> StreamBuilder({
Key? key,
int? initialData,
Stream<int>? stream,
required Widget Function(BuildContext, AsyncSnapshot<int>) builder,
})
①initialData
初期 snapshot の作成に使用されるデータを指定する
型は int 型
②stream
データを流すイベントを指定する
StreamBuilder はこの stream から流れるイベントを取得しています
型は Stream<int> 型
③builder
この中で取得したデータをもとに作る画面を設定する
型は Widget Function(BuildContext, AsyncSnapshot<int>) 型
最後に
今回は StreamBuilder を紹介しました。
この StreamBuilder は個人的には Firebase を使うとよく見る Widget だなと思います。 以前アウトプットした FutureBuilder と同様に、アプリを作るなら非同期処理を扱うことはよくあることなので、勉強して損はないと思いました。
本記事が少しでも為になれば嬉しいです。
次は #15 InheritedModel です。またお会いしましょう。
参考記事
Discussion