StreamBuilder — ένα widget Flutter που ανακατασκευάζει αυτόματα τη διεπαφή όταν λαμβάνει νέα δεδομένα από ένα ασύγχρονο ρεύμα. Σε αντίθεση με το FutureBuilder που λειτουργεί με εφάπαξ αποτέλεσμα, το StreamBuilder υποστηρίζει συνεχή ενημέρωση του UI καθ' όλη τη διάρκεια ζωής του Stream. Σύμφωνα με την επίσημη τεκμηρίωση του Flutter (2026), το StreamBuilder χρησιμοποιείται σε εφαρμογές πραγματικού χρόνου: συνομιλίες, ροές ειδήσεων, παρακολούθηση αισθητήρων και χρηματοοικονομικά tickers. Είναι ένα βασικό εργαλείο αντιδραστικού προγραμματισμού, όπου το UI αντικατοπτρίζει την κατάσταση των δεδομένων χωρίς χειροκίνητες κλήσεις setState.
Κύρια Σημεία
StreamBuilder — είναι ένα widget από το πακέτο Flutter SDK που εγγράφεται σε ένα Stream και ανακατασκευάζει το θυγατρικό του στοιχείο σε κάθε νέο συμβάν του ρεύματος. Το StreamBuilder δέχεται ένα αντικείμενο Stream και επιστρέφει ένα widget με βάση το τελευταίο snapshot που αποκτήθηκε από το ρεύμα.
Στην αρχιτεκτονική Flutter, το StreamBuilder ανήκει στην ομάδα των Builder-widget που διαχωρίζουν την κατασκευή του UI από την κατάσταση των δεδομένων. Σε αντίθεση με το StatefulWidget, όπου η αλλαγή κατάστασης απαιτεί ρητή κλήση setState, το StreamBuilder αντιδρά σε ασύγχρονα συμβάντα αυτόματα, απλοποιώντας τον κώδικα και μειώνοντας τον κίνδυνο σφαλμάτων συγχρονισμού.
Σε αντίθεση με το FutureBuilder, που επεξεργάζεται μια μεμονωμένη ασύγχρονη τιμή, το StreamBuilder είναι σχεδιασμένο για συνεχή ρεύματα δεδομένων. Το FutureBuilder τελειώνει μετά τη λήψη του πρώτου αποτελέσματος, ενώ το StreamBuilder συνεχίζει να ακούει το ρεύμα και να ενημερώνει το UI σε κάθε νέο συμβάν.
Το StreamBuilder χρησιμοποιείται σε όλα τα σενάρια όπου τα δεδομένα φτάνουν συνεχώς: συνδέσεις WebSocket, callback αισθητήρων, ειδοποιήσεις Firebase, ουρές συμβάντων Bluetooth και μεταδόσεις κατάστασης εφαρμογής μέσω BLoC. Σύμφωνα με ανάλυση έργων Flutter στο GitHub (2025), το StreamBuilder βρίσκεται στα τρία πιο συχνά χρησιμοποιούμενα Builder-widget μαζί με τα FutureBuilder και LayoutBuilder.
Συμπέρασμα: χρησιμοποιήστε το StreamBuilder παντού όπου το UI πρέπει να αντικατοπτρίζει συνεχώς μεταβαλλόμενα δεδομένα, αποφεύγοντας τη χειροκίνητη διαχείριση κατάστασης μέσω StatefulWidget.
StreamBuilder εγγράφεται στο Stream τη στιγμή της κατασκευής και απεγγράφεται κατά την καταστροφή του widget. Κάθε φορά που το Stream εκπέμπει ένα συμβάν, το StreamBuilder λαμβάνει ένα νέο AsyncSnapshot και καλεί τη συνάρτηση builder για ανακατασκευή του UI.
Η διαδικασία αποτελείται από τρία στάδια. Πρώτο: το StreamBuilder δημιουργεί μια εγγραφή στο παρεχόμενο Stream μέσω της μεθόδου stream.listen. Δεύτερο: σε κάθε συμβάν, το StreamBuilder ενημερώνει το εσωτερικό AsyncSnapshot και επισημαίνει το widget ως “βρώμικο” για ανακατασκευή. Τρίτο: το πλαίσιο καλεί τη συνάρτηση builder με το νέο snapshot και το UI εμφανίζει τα τρέχοντα δεδομένα.
Σημαντικό: το StreamBuilder χρησιμοποιεί εσωτερικά StreamSubscription. Εάν το Stream παρέχεται απευθείας, το StreamBuilder εγγράφεται μία φορά κατά την αρχικοποίηση. Εάν το Stream αλλάξει (π.χ., κατά την ανακατασκευή του γονέα), το StreamBuilder απεγγράφεται από το παλιό ρεύμα και εγγράφεται στο νέο. Αυτή η συμπεριφορά ελέγχεται από τις παραμέτρους initialData και buildWhen, οι οποίες επιτρέπουν τη βελτιστοποίηση του αριθμού των ανακατασκευών.
Συμπέρασμα: η κατανόηση του κύκλου ζωής της εγγραφής είναι η βάση της σωστής χρήσης του StreamBuilder. Η λανθασμένη διαχείριση των ρευμάτων οδηγεί σε διαρροές μνήμης ή παλιά δεδομένα στο UI.
Η ιδιότητα connectionState του αντικειμένου AsyncSnapshot καθορίζει σε ποιο στάδιο εργασίας με το ρεύμα βρίσκεται το StreamBuilder. Διακρίνονται τέσσερις καταστάσεις: none, waiting, active, done.
None — η αρχική κατάσταση όταν το Stream δεν έχει αρχίσει ακόμα να μεταδίδει δεδομένα. Σε αυτή την κατάσταση, το snapshot.connectionState είναι ίσο με ConnectionState.none και το snapshot.data είναι null. Συνήθως σε αυτή την κατάσταση εμφανίζεται ένα placeholder ή αναμονή για το πρώτο συμβάν. Εάν το Stream δεν παρέχει αρχικά δεδομένα, το StreamBuilder ξεκινά από αυτή την κατάσταση.
Waiting — η κατάσταση αναμονής για δεδομένα από το ασύγχρονο ρεύμα. Το Stream είναι ενεργό, αλλά τα δεδομένα δεν έχουν φτάσει ακόμα. Αυτή η κατάσταση προκύπτει, για παράδειγμα, κατά τη φόρτωση δεδομένων από το δίκτυο ή το άνοιγμα μιας μακροχρόνιας σύνδεσης. Σε αυτή την κατάσταση συνήθως εμφανίζεται CircularProgressIndicator ή σκελετός φόρτωσης.
Active — το ρεύμα εκπέμπει δεδομένα και το UI εμφανίζει τρέχουσες πληροφορίες. Σε αυτή την κατάσταση, το snapshot.hasData είναι true και το snapshot.data περιέχει την τελευταία τιμή από το ρεύμα. Εάν το Stream είναι Broadcast Stream, η ενεργή κατάσταση μπορεί να συνυπάρχει με αναμονή για νέα δεδομένα.
Done — το ρεύμα έχει ολοκληρωθεί, δεν θα υπάρχουν νέα δεδομένα. Το Snapshot.data περιέχει την τελευταία τιμή που μεταδόθηκε πριν από το κλείσιμο του ρεύματος. Εάν το ρεύμα ολοκληρώθηκε με επιτυχία, το snapshot.hasError είναι false. Αυτή η κατάσταση χρησιμοποιείται για την εμφάνιση του τελικού αποτελέσματος: μήνυμα “Η φόρτωση ολοκληρώθηκε” ή μετάβαση στην επόμενη οθόνη.
Συμπέρασμα: κατά την κατασκευή UI μέσω StreamBuilder, πρέπει να γίνεται επεξεργασία και των τεσσάρων καταστάσεων, ώστε η διεπαφή να εμφανίζει σωστά φόρτωση, δεδομένα, σφάλματα και ολοκλήρωση.
StreamController — είναι μια κλάση από το πακέτο dart:async που δημιουργεί και διαχειρίζεται ένα Stream. Το StreamController επιτρέπει την προσθήκη δεδομένων, την επεξεργασία σφαλμάτων και το κλείσιμο του ρεύματος, ελέγχοντας τον κύκλο ζωής του.
Το StreamController διατίθεται σε δύο τύπους: single-subscription (ένας συνδρομητής) και broadcast (πολλοί συνδρομητές). Ο ελεγκτής single-subscription δέχεται μόνο έναν ακροατή κάθε φορά — η εκ νέου εγγραφή προκαλεί εξαίρεση. Ο ελεγκτής broadcast επιτρέπει σε πολλά StreamBuilder να ακούν ταυτόχρονα το ίδιο ρεύμα, το οποίο είναι χρήσιμο για BLoC και κοινή κατάσταση εφαρμογής.
Κατά τη δημιουργία StreamController μέσω StreamController<T>.broadcast(), τα δεδομένα που προστέθηκαν πριν από την πρώτη εγγραφή δεν αναπαράγονται στον νέο συνδρομητή. Εάν χρειάζεται να λάβετε την τελευταία τιμή κατά τη σύνδεση, χρησιμοποιήστε το BehaviourSubject από το πακέτο rxdart, το οποίο αποθηκεύει προσωρινά το τελευταίο συμβάν.
Μετά την ολοκλήρωση της εργασίας με τον ελεγκτή, πρέπει να κληθεί το 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();
}
});
}
}
Στο παράδειγμα, δημιουργείται ένας ελεγκτής για τη δημιουργία αριθμών από 0 έως 10 με διάστημα 1 δευτερολέπτου. Μετά την επίτευξη του 10, καλείται το close και το ρεύμα ολοκληρώνεται. Το StreamBuilder, εγγεγραμμένο στο stream αυτού του ελεγκτή, θα εμφανίζει κάθε νέα τιμή.
Παράδειγμα 2 — χρήση StreamBuilder με Broadcast Stream για εμφάνιση δεδομένων από πολλαπλές πηγές.
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('Error: ${snapshot.error}');
}
return Text('Data: ${snapshot.data}');
},
)
Το δεύτερο παράδειγμα δείχνει την επεξεργασία όλων των καταστάσεων: initialData για αρχική εμφάνιση, waiting για δείκτη φόρτωσης, hasError για σφάλματα και data για επιτυχές αποτέλεσμα. Αυτό το μοτίβο είναι το πρότυπο για κώδικα παραγωγής με StreamBuilder.
Συμπέρασμα: χρησιμοποιήστε το initialData για να αποφύγετε την κενή οθόνη την πρώτη στιγμή και να επεξεργάζεστε πάντα το hasError για σωστή εμφάνιση σφαλμάτων στον χρήστη.
Λάθος 1: δημιουργία νέου Stream σε κάθε ανακατασκευή του γονέα. Εάν το Stream παρέχεται μέσω μιας έκφρασης που δημιουργεί νέο αντικείμενο σε κάθε κατασκευή, το StreamBuilder απεγγράφεται από το παλιό ρεύμα και εγγράφεται στο νέο, προκαλώντας ατέρμονο κύκλο ανακατασκευών. Λύση: χρησιμοποιήστε remembered μεταβλητή ή StatefulWidget με σταθερό Stream.
Λάθος 2: έλλειψη επεξεργασίας σφαλμάτων. Το Stream μπορεί να εκπέμπει σφάλματα μέσω controller.sink.addError, και εάν ο builder δεν ελέγχει το snapshot.hasError, ο χρήστης βλέπει κενή οθόνη ή ατέρμονη φόρτωση. Λύση: ελέγχετε πάντα το hasError και εμφανίζετε κατανοητό μήνυμα.
Λάθος 3: διαρροή μνήμης λόγω μη κλεισίματος StreamController. Εάν ο ελεγκτής δεν κλείσει στο dispose, το ρεύμα συνεχίζει να υπάρχει και ο GC δεν ελευθερώνει μνήμη. Λύση: καλείτε το controller.close() στο dispose και ακούτε το συμβάν done για τελικές ενέργειες.
Λάθος 4: χρήση StreamBuilder με αργή συνάρτηση builder. Επειδή ο builder καλείται σε κάθε συμβάν του ρεύματος, οι βαριοί υπολογισμοί εντός του οδηγούν σε απώλεια καρέ. Λύση: μεταφέρετε τους υπολογισμούς σε ξεχωριστό isolate ή χρησιμοποιήστε Stream.map για μετασχηματισμό δεδομένων.
Συμπέρασμα: το StreamBuilder είναι ένα ισχυρό αλλά απαιτητικό εργαλείο. Παρακολουθείτε τον κύκλο ζωής του Stream, επεξεργάζεστε σφάλματα και αποφεύγετε βαριές λειτουργίες στο builder.
Συχνές Ερωτήσεις
FutureBuilder είναι σχεδιασμένο για εφάπαξ ασύγχρονο αποτέλεσμα: εγγράφεται σε ένα Future, λαμβάνει μία τιμή και ολοκληρώνει τη δουλειά του. Το StreamBuilder εγγράφεται σε ένα Stream, το οποίο μπορεί να εκπέμπει πολλές τιμές με την πάροδο του χρόνου, και ανακατασκευάζει το UI σε κάθε νέο συμβάν.
AsyncSnapshot — ένα αμετάβλητο αντικείμενο που περιέχει την τρέχουσα κατάσταση εγγραφής (connectionState), την τελευταία ληφθείσα τιμή (data) και το αντικείμενο σφάλματος (error), εάν το ρεύμα εκπέμψει εξαίρεση.
Σφάλμα επεξεργάζεται μέσω των ιδιοτήτων snapshot.hasError και snapshot.error στη συνάρτηση builder. Εάν το ρεύμα εκπέμπει σφάλμα με τη μέθοδο sink.addError, το AsyncSnapshot λαμβάνει error και ο builder πρέπει να εμφανίσει κατάλληλο μήνυμα ή εναλλακτικό UI.
Ναι, εάν το Stream είναι broadcast (δημιουργημένο μέσω StreamController.broadcast). Το single-subscription Stream επιτρέπει μόνο έναν συνδρομητή. Για κοινή χρήση ενός ρεύματος μεταξύ πολλών widget, χρησιμοποιήστε ελεγκτή broadcast ή το πακέτο rxdart με BehaviourSubject.
Χρησιμοποιήστε την παράμετρο buildWhen για φιλτράρισμα συμβάντων στα οποία χρειάζεται ανακατασκευή του UI. Επίσης, εφαρμόστε Stream.transformer ή Stream.where για φιλτράρισμα δεδομένων πριν από τη μετάδοση στο StreamBuilder.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης