StreamBuilderは、非同期ストリームから新しいデータを受け取ると自動的にインターフェースを再構築するFlutterウィジェットです。単一の結果を扱うFutureBuilderとは異なり、StreamBuilderはStreamのライフサイクル全体を通じて継続的なUI更新をサポートします。公式のFlutterドキュメント(2026年)によると、StreamBuilderはリアルタイムアプリケーション(チャット、ニュースフィード、センサーモニタリング、金融ティッカー)で使用されています。これはリアクティブプログラミングの重要なツールであり、UIは手動のsetState呼び出しなしでデータの状態を反映します。
重要なポイント
StreamBuilderはFlutter SDKパッケージのウィジェットで、Streamに購読し、新しいストリームイベントごとに子要素を再構築します。StreamBuilderはStreamオブジェクトを受け取り、ストリームから受信した最新のスナップショットに基づいてウィジェットを返します。
Flutterアーキテクチャにおいて、StreamBuilderはUI構築をデータ状態から分離するBuilderウィジェットグループに属します。状態変更に明示的なsetState呼び出しが必要なStatefulWidgetとは異なり、StreamBuilderは非同期イベントに自動的に反応し、コードを簡素化して同期エラーのリスクを低減します。
単一の非同期値を扱うFutureBuilderとは異なり、StreamBuilderは継続的なデータストリーム向けに設計されています。FutureBuilderは最初の結果を受け取ると終了しますが、StreamBuilderはストリームをリッスンし続け、新しいイベントごとにUIを更新します。
StreamBuilderはデータが継続的に到着するすべてのシナリオで使用されます:WebSocket接続、センサーコールバック、Firebase通知、Bluetoothイベントキュー、BLoCを介したアプリケーション状態のブロードキャスト。GitHub上のFlutterプロジェクトの分析(2025年)によると、StreamBuilderはFutureBuilderやLayoutBuilderと並んで最も使われるBuilderウィジェットのトップ3に入っています。
まとめ:UIが継続的に変化するデータを反映する必要がある場合は、StatefulWidgetによる手動状態管理を避け、StreamBuilderを使用してください。
StreamBuilderはビルド時にStreamに購読し、ウィジェットが破棄されると購読を解除します。Streamがイベントを発行するたびに、StreamBuilderは新しいAsyncSnapshotを受け取り、UIを再構築するためにbuilder関数を呼び出します。
プロセスは3つの段階で構成されます。第一に、StreamBuilderはstream.listenメソッドを介して渡されたStreamに購読を作成します。第二に、各イベントでStreamBuilderは内部のAsyncSnapshotを更新し、再構築のためにウィジェットをダーティとしてマークします。第三に、フレームワークが新しいスナップショットでbuilder関数を呼び出し、UIが現在のデータを表示します。
重要:StreamBuilderは内部的にStreamSubscriptionを使用します。Streamが直接渡された場合、StreamBuilderは初期化時に一度だけ購読します。Streamが変更された場合(例えば親の再構築時)、StreamBuilderは古いストリームから購読を解除し、新しいストリームに購読します。この動作はinitialDataとbuildWhenパラメータによって制御され、再構築の回数を最適化できます。
まとめ:購読のライフサイクルを理解することがStreamBuilderを正しく使用するための基本です。誤ったストリーム管理はメモリリークやUIの古いデータ表示につながります。
AsyncSnapshotオブジェクトのconnectionStateプロパティは、StreamBuilderがストリーム処理のどの段階にあるかを決定します。none、waiting、active、doneの4つの状態があります。
NoneはStreamがまだデータ送信を開始していない初期状態です。この状態では、snapshot.connectionStateはConnectionState.noneで、snapshot.dataはnullです。通常、この状態ではプレースホルダーまたは待機インジケーターが表示されます。Streamが初期データを提供しない場合、StreamBuilderはこの状態から始まります。
Waitingは非同期ストリームからのデータを待機している状態です。Streamはアクティブですが、データはまだ到着していません。この状態は、例えばネットワークからデータを読み込む時や長期間の接続を開く時に発生します。この状態ではCircularProgressIndicatorやスケルトンローダーを表示するのが一般的です。
Active — ストリームがデータを発行しており、UIが最新情報を表示しています。この状態では、snapshot.hasDataはtrueで、snapshot.dataにはストリームの最新値が含まれます。ストリームがBroadcast Streamの場合、アクティブ状態は新しいデータの待機と共存できます。
Done — ストリームが完了し、新しいデータは届きません。Snapshot.dataにはストリームが閉じられる前に渡された最後の値が含まれます。ストリームが正常に完了した場合、snapshot.hasErrorはfalseです。この状態は最終結果の表示に使用されます:「読み込み完了」というメッセージや次の画面への遷移などです。
まとめ:StreamBuilderを介してUIを構築する際は、インターフェースが読み込み、データ、エラー、完了を正しく表示できるよう、4つの状態すべてを処理する必要があります。
StreamControllerはdart:asyncパッケージのクラスで、Streamを作成および管理します。StreamControllerはデータの追加、エラーの処理、ストリームのクローズ、ライフサイクルの制御を可能にします。
StreamControllerにはsingle-subscription(1つのサブスクライバー)とbroadcast(複数のサブスクライバー)の2種類があります。Single-subscriptionコントローラーは一度に1つのリスナーのみを受け入れます。2回目の購読は例外をスローします。Broadcastコントローラーは複数のStreamBuilderが同時に同じストリームをリッスンすることを可能にし、BLoCや共有アプリケーション状態に役立ちます。
StreamController<T>.broadcast()を介してStreamControllerを作成する場合、最初の購読前に追加されたデータは新しいサブスクライバーには再生されません。接続時に最新の値を取得するには、最後のイベントをキャッシュするrxdartパッケージのBehaviourSubjectを使用します。
コントローラーでの作業が終了したら、controller.close()を呼び出す必要があります。closeを呼び出さないと、リソースリークが発生します:ストリームは開いたまま、サブスクライバーはメモリに残り、GCは関連オブジェクトを解放しません。
まとめ:StreamControllerは明示的なライフサイクル管理と共に使用してください。Single-subscriptionストリームには標準コントローラーを、共有状態にはbroadcastコントローラーまたはBehaviourSubjectを使用してください。
例1はStreamControllerとStreamBuilderを使用したカウントダウンタイマーを示しています。
import 'dart:async';
class TimerWidget extends StatefulWidget {
const TimerWidget({super.key});
final StreamController<int> controller = StreamController<int>();
void startTimer() {
int count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
controller.sink.add(count++);
if (count > 10) {
controller.close();
timer.cancel();
}
});
}
}
この例では、1秒間隔で0から10までの数値を生成するコントローラーが作成されています。10に達するとcloseが呼び出され、ストリームが終了します。このコントローラーのストリームに購読しているStreamBuilderは、新しい値ごとに表示を更新します。
例2 — 複数のソースからのデータを表示するためのBroadcast StreamとStreamBuilderの使用。
final StreamController<String> broadcastController =
StreamController<String>.broadcast();
StreamBuilder<String>(
stream: broadcastController.stream,
initialData: 'Waiting for data...',
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Text('エラー: ${snapshot.error}');
}
return Text('データ: ${snapshot.data}');
},
)
2つ目の例では、すべての状態の処理を示しています:initialData(初期表示)、waiting(読み込みインジケーター)、hasError(エラー)、data(成功結果)。このパターンはStreamBuilderを使用するプロダクションコードの標準です。
まとめ:最初の瞬間に空の画面を避けるためにinitialDataを使用し、ユーザーにエラーを正しく表示するために常にhasErrorを処理してください。
間違い1:親の再構築ごとに新しいStreamを作成する。Streamがビルドごとに新しいオブジェクトを作成する式を介して渡されると、StreamBuilderは古いストリームから購読解除し新しいストリームに購読するため、無限の再構築ループが発生します。解決策:remembered変数または固定のStreamを持つStatefulWidgetを使用してください。
間違い2:エラー処理の欠如。Streamはcontroller.sink.addErrorを介してエラーを発行する可能性があり、builderがsnapshot.hasErrorをチェックしない場合、ユーザーは空の画面や無限の読み込みを見ることになります。解決策:常にhasErrorをチェックし、明確なメッセージを表示してください。
間違い3:クローズされていないStreamControllerによるメモリリーク。コントローラーがdisposeで閉じられない場合、ストリームは存在し続け、GCはメモリを解放しません。解決策:disposeでcontroller.close()を呼び出し、最終処理のためにdoneイベントをリッスンしてください。
間違い4:遅いbuilder関数でのStreamBuilderの使用。builderはストリームイベントごとに呼び出されるため、内部での重い計算はフレームドロップを引き起こします。解決策:計算を別のアイソレートに移動するか、データ変換にStream.mapを使用してください。
まとめ:StreamBuilderは強力ですが要求の厳しいツールです。Streamのライフサイクルを監視し、エラーを処理し、builderでの重い操作を避けてください。
よくある質問
FutureBuilderは単一の非同期結果用に設計されています:Futureに購読し、1つの値を受け取り、終了します。StreamBuilderはStreamに購読し、時間とともに複数の値を発行でき、新しいイベントごとにUIを再構築します。
AsyncSnapshotは不変オブジェクトで、現在の購読状態(connectionState)、最後に受信した値(data)、およびストリームが例外を発行した場合のエラーオブジェクト(error)を含みます。
エラーはbuilder関数のsnapshot.hasErrorおよびsnapshot.errorプロパティを介して処理されます。ストリームがsink.addErrorを介してエラーを発行すると、AsyncSnapshotがエラーを受け取り、builderは適切なメッセージまたはフォールバックUIを表示する必要があります。
はい、Streamがbroadcast(StreamController.broadcastを介して作成)の場合可能です。Single-subscription Streamは1つのサブスクライバーのみ許可します。複数のウィジェット間で1つのストリームを共有するには、broadcastコントローラーまたはBehaviourSubjectを含むrxdartパッケージを使用してください。
UI再構築をトリガーするイベントをフィルタリングするにはbuildWhenパラメータを使用します。また、StreamBuilderにデータを渡す前にフィルタリングするためにStream.transformerやStream.whereを適用することもできます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。