Flutter:StreamBuilderを用いて、動的にWidgetを更新する
StreamBuilderを用いるとStreamで送られてくるイベント(データ)に応じて容易にWidgetを更新することができます。
学習用に作成したカウントダウンタイマーでは、次の実装で利用しています。
- カウントダウンタイマーの状態に応じて、ボタンのアイコンをスタート/ポーズ/ストップに切替える。
- タイムアップまでの残り時間の表示を更新する。
本記事ではStreamBuilderを利用した実装について、紹介したいと思います。
BLoCパターン
StreamBuilderを用いる際は、まずBLoC(Business Logic Component)パターンの理解から始めると良いと思います。
とは言いつつ、BLoCパターンは他に良い説明をされているサイトがありますので、本記事での説明は省略します。
大まかにだけ述べますと、BLoCのインターフェースはStreamです。入力(ユーザー操作)をStreamを介して受け取り、入力に対する応答をStreamを介して出力する構成となっています。
そして、その応答は後述するStreamBuilderで処理する事になると思います。
StreamBuilder
StreamBuilderは、あるStreamを監視して、イベント(データ)が通知される度にWidgetを更新(再描画)します。
「あるStreamを監視して」と述べましたが、BLoCパターンを用いるとBLoCの出力(応答)にあたるStreamを監視対象にします。
カウントダウンタイマーにおけるStreamBuilderを用いた実装
実装方法については、カウントダウンタイマーの次のユースケースにおけるスタートボタン(Widget)の実装で説明します。
- カウントダウンタイマーは、制限時間が設定され、ストップしている状態である。
- ユーザがスタートボタンを押下する。
- カウントダウンタイマーはスタート状態になり、カウントダウンを開始する。
- スタートボタンをポーズボタンのアイコンに更新する。
カウントダウンタイマーのBLoCは次のコードの3行目でインスタンス化しているCountDownTimerBlocです。
CountDownTimerBlocの主な機能は次の通りです。
- カウントダウンタイマーのスタートやポーズ、リセットを実行する。
- カウントダウンタイマーの情報(「スタート、ストップなどの状態」や「タイムアップまでの時間」など)を通知する。
次にスタートボタンですが、FloatingActionButtonクラスで実装しています。
まず、スタートボタンを押下したときの動作ですが、9行目の onPressed
で設定しています。
ここでは、CountDownTimerBlocに対して、カウントをスタートするというイベントをStreamを介して通知しています。
(カウントのスタートとポーズは、状態をどちらかに切替えるだけなので、イベントを一つにまとめています。)
CountDownTimerBlocはイベントを受け取ると、内部でタイマースタートの処理をして、その結果を応答用のStreamを介して外部に通知しています。
CountDownTimerBlocの応答用Streamは、カウントダウンタイマーの情報(=CountDownTimerInfo)を通知するようにしています。
そして、その応答用Streamは12行目からのStreamBuilderで利用しています。
StremBuilderのコンストラクタに次の設定をするだけで、スタートボタンのWidgetの実装は完了です。
- stream
監視対象のStreamを設定します。
ここでは、CountDownTimerBlocの応答用Streamを設定します。 - builder
Widgetを更新するリスナーを設定します。
このリスナーはWidget初回build時、streamにイベントが通知される度に動作します。
リスナーの第二引数のsnapShotにはinitialData、もしくはstreamに流れるある時点のイベント(データ)が格納されています。
ここでは、snapShotに格納されているカウントダウンタイマーの状態に応じてIconを更新する処理を設定します。 - initialData
snapShotの初期値を生成するための値を設定します。
Widget初回build時にstreamから得られるデータはないので、このパラメータで設定します。
設定しない場合、snapShotの初期値はinitialData=nullで生成されます。
今回は、StreamBuilderを用いてWidgetを更新する実装について紹介しました。
BLoCパターンを用いるとView側とビジネスロジック側の実装を分けることができて良いと思いました。
Providerの理解を後回しにしたので、後日トライします。