StreamBuilder ist ein Flutter-Widget, das die Benutzeroberfläche automatisch neu aufbaut, wenn neue Daten aus einem asynchronen Stream eintreffen. Im Gegensatz zu FutureBuilder, der mit einem einzelnen Ergebnis arbeitet, unterstützt StreamBuilder kontinuierliche UI-Aktualisierungen während des gesamten Lebenszyklus eines Streams. Laut der offiziellen Flutter-Dokumentation (2026) wird StreamBuilder in Echtzeitanwendungen verwendet: Chats, Newsfeeds, Sensorüberwachung und Finanzticker. Es ist ein Schlüsselwerkzeug der reaktiven Programmierung, bei dem die UI den Datenzustand ohne manuelle setState-Aufrufe widerspiegelt.
Wichtige Punkte
StreamBuilder ist ein Widget aus dem Flutter SDK Paket, das einen Stream abonniert und sein Kindelement bei jedem neuen Stream-Ereignis neu aufbaut. StreamBuilder akzeptiert ein Stream-Objekt und gibt ein Widget basierend auf dem neuesten vom Stream empfangenen Snapshot zurück.
In der Flutter-Architektur gehört StreamBuilder zur Gruppe der Builder-Widgets, die die UI-Konstruktion vom Datenzustand trennen. Im Gegensatz zu StatefulWidget, bei dem eine Zustandsänderung einen expliziten setState-Aufruf erfordert, reagiert StreamBuilder automatisch auf asynchrone Ereignisse, was den Code vereinfacht und das Risiko von Synchronisationsfehlern reduziert.
Im Gegensatz zu FutureBuilder, der einen einzelnen asynchronen Wert behandelt, ist StreamBuilder für kontinuierliche Datenströme konzipiert. FutureBuilder endet nach Erhalt des ersten Ergebnisses, während StreamBuilder weiterhin den Stream abhört und die UI bei jedem neuen Ereignis aktualisiert.
StreamBuilder wird in allen Szenarien verwendet, in denen Daten kontinuierlich eintreffen: WebSocket-Verbindungen, Sensor-Callbacks, Firebase-Benachrichtigungen, Bluetooth-Ereigniswarteschlangen und Anwendungszustandsübertragung über BLoC. Laut einer Analyse von Flutter-Projekten auf GitHub (2025) gehört StreamBuilder zu den drei am häufigsten verwendeten Builder-Widgets neben FutureBuilder und LayoutBuilder.
Fazit: Verwenden Sie StreamBuilder überall dort, wo die UI kontinuierlich wechselnde Daten widerspiegeln muss, und vermeiden Sie manuelle Zustandsverwaltung über StatefulWidget.
StreamBuilder abonniert einen Stream zum Zeitpunkt der Erstellung und kündigt das Abonnement bei der Zerstörung des Widgets. Jedes Mal, wenn der Stream ein Ereignis ausgibt, erhält StreamBuilder einen neuen AsyncSnapshot und ruft die Builder-Funktion zum Neuaufbau der UI auf.
Der Prozess besteht aus drei Schritten. Erstens: StreamBuilder erstellt ein Abonnement für den übergebenen Stream über die Methode stream.listen. Zweitens: Bei jedem Ereignis aktualisiert StreamBuilder den internen AsyncSnapshot und markiert das Widget als schmutzig für den Neuaufbau. Drittens: Das Framework ruft die Builder-Funktion mit dem neuen Snapshot auf, und die UI zeigt die aktuellen Daten an.
Wichtig: StreamBuilder verwendet intern StreamSubscription. Wenn der Stream direkt übergeben wird, abonniert StreamBuilder einmal bei der Initialisierung. Wenn sich der Stream ändert (z.B. bei einem Neuaufbau des Eltern-Widgets), kündigt StreamBuilder das alte Abonnement und abonniert den neuen Stream. Dieses Verhalten wird durch die Parameter initialData und buildWhen gesteuert, die eine Optimierung der Anzahl von Neuaufbauten ermöglichen.
Fazit: Das Verständnis des Abonnement-Lebenszyklus ist die Grundlage für die korrekte Verwendung von StreamBuilder. Falsches Stream-Management führt zu Speicherlecks oder veralteten Daten in der UI.
Die Eigenschaft connectionState des AsyncSnapshot-Objekts bestimmt, in welcher Phase der Stream-Verarbeitung sich StreamBuilder befindet. Es gibt vier Zustände: none, waiting, active, done.
None ist der Anfangszustand, wenn der Stream noch keine Daten gesendet hat. In diesem Zustand ist snapshot.connectionState gleich ConnectionState.none und snapshot.data ist null. Normalerweise wird in diesem Zustand ein Platzhalter oder Warteindikator angezeigt. Wenn der Stream keine Anfangsdaten liefert, beginnt StreamBuilder in diesem Zustand.
Waiting ist der Zustand des Wartens auf Daten aus einem asynchronen Stream. Der Stream ist aktiv, aber die Daten sind noch nicht eingetroffen. Dieser Zustand tritt zum Beispiel beim Laden von Daten aus dem Netzwerk oder beim Öffnen einer langlebigen Verbindung auf. In diesem Zustand ist es üblich, einen CircularProgressIndicator oder einen Skelett-Loader anzuzeigen.
Active — der Stream gibt Daten aus und die UI zeigt aktuelle Informationen an. In diesem Zustand ist snapshot.hasData true und snapshot.data enthält den neuesten Wert aus dem Stream. Wenn der Stream ein Broadcast Stream ist, kann der aktive Zustand mit dem Warten auf neue Daten koexistieren.
Done — der Stream ist abgeschlossen, es werden keine neuen Daten eintreffen. Snapshot.data enthält den letzten Wert, der vor dem Schließen des Streams übergeben wurde. Wenn der Stream erfolgreich abgeschlossen wurde, ist snapshot.hasError false. Dieser Zustand wird verwendet, um das Endergebnis anzuzeigen: eine Nachricht wie „Laden abgeschlossen“ oder ein Übergang zum nächsten Bildschirm.
Fazit: Beim Erstellen der UI über StreamBuilder müssen alle vier Zustände behandelt werden, damit die Oberfläche das Laden, Daten, Fehler und den Abschluss korrekt anzeigt.
StreamController ist eine Klasse aus dem Paket dart:async, die einen Stream erstellt und verwaltet. StreamController ermöglicht das Hinzufügen von Daten, die Behandlung von Fehlern und das Schließen des Streams sowie die Steuerung seines Lebenszyklus.
StreamController gibt es in zwei Typen: Single-Subscription (ein Abonnent) und Broadcast (mehrere Abonnenten). Ein Single-Subscription-Controller akzeptiert nur einen Hörer auf einmal — ein zweites Abonnement löst eine Ausnahme aus. Ein Broadcast-Controller ermöglicht mehreren StreamBuilder-Instanzen, gleichzeitig denselben Stream zu hören, was für BLoC und gemeinsamen Anwendungszustand nützlich ist.
Bei der Erstellung eines StreamControllers über StreamController<T>.broadcast() werden Daten, die vor dem ersten Abonnement hinzugefügt wurden, nicht an neue Abonnenten weitergegeben. Um den neuesten Wert bei der Verbindung zu erhalten, verwenden Sie BehaviourSubject aus dem Paket rxdart, das das letzte Ereignis zwischenspeichert.
Nach Abschluss der Arbeit mit dem Controller muss controller.close() aufgerufen werden. Das Unterlassen von close führt zu Ressourcenlecks: Der Stream bleibt offen, Abonnenten bleiben im Speicher und der GC gibt die zugehörigen Objekte nicht frei.
Fazit: Verwenden Sie StreamController mit explizitem Lebenszyklus-Management. Für Single-Subscription-Streams verwenden Sie den Standard-Controller, für gemeinsamen Zustand einen Broadcast-Controller oder BehaviourSubject.
Beispiel 1 zeigt einen Countdown-Timer mit StreamController und 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();
}
});
}
}
Im Beispiel wird ein Controller erstellt, um Zahlen von 0 bis 10 in 1-Sekunden-Intervallen zu generieren. Nach Erreichen von 10 wird close aufgerufen und der Stream beendet. StreamBuilder, der den Stream dieses Controllers abonniert hat, zeigt jeden neuen Wert an.
Beispiel 2 — Verwendung von StreamBuilder mit einem Broadcast Stream zur Anzeige von Daten aus mehreren Quellen.
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('Fehler: ${snapshot.error}');
}
return Text('Daten: ${snapshot.data}');
},
)
Das zweite Beispiel zeigt die Behandlung aller Zustände: initialData für die anfängliche Anzeige, waiting für den Ladeindikator, hasError für Fehler und data für das erfolgreiche Ergebnis. Dieses Muster ist der Standard für Produktionscode mit StreamBuilder.
Fazit: Verwenden Sie initialData, um einen leeren Bildschirm zu Beginn zu vermeiden, und behandeln Sie immer hasError, um Fehler korrekt anzuzeigen.
Fehler 1: Erstellen eines neuen Streams bei jedem Neuaufbau des Eltern-Widgets. Wenn der Stream über einen Ausdruck übergeben wird, der bei jedem Aufbau ein neues Objekt erstellt, kündigt StreamBuilder das alte Abonnement und abonniert den neuen Stream, was zu einer Endlosschleife von Neuaufbauten führt. Lösung: Verwenden Sie eine remembered Variable oder ein StatefulWidget mit einem festen Stream.
Fehler 2: Fehlende Fehlerbehandlung. Ein Stream kann Fehler über controller.sink.addError ausgeben, und wenn der Builder nicht snapshot.hasError überprüft, sieht der Benutzer einen leeren Bildschirm oder unendliches Laden. Lösung: Überprüfen Sie immer hasError und zeigen Sie eine klare Nachricht an.
Fehler 3: Speicherlecks aufgrund eines nicht geschlossenen StreamControllers. Wenn der Controller nicht in dispose geschlossen wird, bleibt der Stream bestehen und der GC gibt den Speicher nicht frei. Lösung: Rufen Sie controller.close() in dispose auf und hören Sie auf das done-Ereignis für abschließende Aktionen.
Fehler 4: Verwendung von StreamBuilder mit einer langsamen Builder-Funktion. Da der Builder bei jedem Stream-Ereignis aufgerufen wird, führen schwere Berechnungen darin zu Frame-Einbrüchen. Lösung: Verlagern Sie Berechnungen in ein separates Isolate oder verwenden Sie Stream.map zur Datentransformation.
Fazit: StreamBuilder ist ein leistungsstarkes, aber anspruchsvolles Werkzeug. Überwachen Sie den Stream-Lebenszyklus, behandeln Sie Fehler und vermeiden Sie schwere Operationen im Builder.
Häufig gestellte Fragen
FutureBuilder ist für ein einzelnes asynchrones Ergebnis konzipiert: Er abonniert einen Future, erhält einen Wert und endet. StreamBuilder abonniert einen Stream, der im Laufe der Zeit mehrere Werte ausgeben kann, und baut die UI bei jedem neuen Ereignis neu auf.
AsyncSnapshot ist ein unveränderliches Objekt, das den aktuellen Abonnementzustand (connectionState), den letzten empfangenen Wert (data) und ein Fehlerobjekt (error) enthält, falls der Stream eine Ausnahme ausgelöst hat.
Fehler werden über die Eigenschaften snapshot.hasError und snapshot.error in der Builder-Funktion behandelt. Wenn der Stream einen Fehler über sink.addError ausgibt, erhält AsyncSnapshot den Fehler, und der Builder sollte eine entsprechende Nachricht oder eine Fallback-UI anzeigen.
Ja, wenn der Stream ein Broadcast-Stream ist (erstellt über StreamController.broadcast). Ein Single-Subscription-Stream erlaubt nur einen Abonnenten. Um einen Stream zwischen mehreren Widgets zu teilen, verwenden Sie einen Broadcast-Controller oder das Paket rxdart mit BehaviourSubject.
Verwenden Sie den Parameter buildWhen, um zu filtern, welche Ereignisse einen UI-Neuaufbau auslösen sollen. Wenden Sie auch Stream.transformer oder Stream.where an, um Daten zu filtern, bevor sie an StreamBuilder übergeben werden.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch