setState() — η βασική μέθοδος της State στο Flutter, που ειδοποιεί το πλαίσιο για αλλαγή δεδομένων και εκκινεί την ανακατασκευή της διεπαφής. Σύμφωνα με την επίσημη τεκμηρίωση του Flutter (Flutter.dev, 2026), το setState είναι ο κύριος μηχανισμός αντιδραστικότητας στο StatefulWidget: χωρίς την κλήση του, το UI δεν θα μάθει για αλλαγές στα πεδία State και θα παραμείνει στην προηγούμενη κατάσταση. Η μέθοδος δέχεται ένα VoidCallback, εντός του οποίου ο προγραμματιστής τροποποιεί τα μεταβλητά πεδία, μετά από το οποίο το Flutter καλεί αυτόματα το build για την ανακατασκευή του widget.
Κύρια Σημεία
setState() — μια ενσωματωμένη μέθοδος της κλάσης State στο Flutter, που προορίζεται για την ειδοποίηση του πλαισίου ότι η εσωτερική κατάσταση του widget άλλαξε και απαιτείται ανακατασκευή του UI. Χωρίς κλήση setState, το Flutter δεν γνωρίζει για αλλαγές — ακόμα κι αν τα πεδία State τροποποιήθηκαν, η διεπαφή θα παραμείνει αμετάβλητη μέχρι την επόμενη αναγκαστική ανακατασκευή από τον γονέα.
Υπογραφή μεθόδου: void setState(VoidCallback fn). Το callback εκτελείται σύγχρονα εντός του setState και μόνο μετά την ολοκλήρωσή του, το State επισημαίνεται ως dirty. Αυτό εγγυάται ότι όλες οι αλλαγές εφαρμόζονται ατομικά πριν από την ανακατασκευή. Σύμφωνα με την Dart Language Specification (Dart Team, 2026), η ατομικότητα του setState αποτρέπει συνθήκες ανταγωνισμού, όπου το build θα μπορούσε να δει μερικώς ενημερωμένη κατάσταση.
Το setState δεν δέχεται ορίσματα, δεν επιστρέφει τιμή και δεν μπορεί να παρακαμφθεί. Είναι μια τελική (sealed) μέθοδος της κλάσης State. Ο προγραμματιστής δεν μπορεί να αλλάξει τη συμπεριφορά του — μπορεί μόνο να το χρησιμοποιεί σύμφωνα με τον προορισμό του. Η προσπάθεια κλήσης setState εκτός State (π.χ. από άλλη κλάση) είναι αδύνατη, καθώς η μέθοδος δηλώνεται στην κλάση State.
Μια κοινή παρανόηση — να νομίζετε ότι το setState αλλάζει από μόνο του την κατάσταση. Αυτό δεν ισχύει. Το setState απλώς καλεί το μεταβιβασμένο callback (στο οποίο ο προγραμματιστής αλλάζει τα πεδία) και στη συνέχεια σηματοδοτεί στο πλαίσιο την ανάγκη για build. Το callback είναι υποχρεωτικό — η μεταβίβαση null ή κενού callback θα προκαλέσει σφάλμα.
Ο μηχανισμός λειτουργίας του setState() μπορεί να χωριστεί σε τέσσερα στάδια. Πρώτο — κλήση της μεθόδου με callback. Δεύτερο — σύγχρονη εκτέλεση του callback, εντός του οποίου τροποποιούνται τα πεδία State. Τρίτο — το State επισημαίνεται ως dirty στο ειδικό πεδίο _dirty. Τέταρτο — στο τέλος του τρέχοντος microtask, το Flutter διατρέχει όλα τα dirty στοιχεία και καλεί το build τους με τη σειρά εμφάνισης στο δέντρο.
Σημαντική λεπτομέρεια: το setState δεν καλεί αμέσως το build. Το Flutter χρησιμοποιεί στρατηγική μαζικής ενημέρωσης: όλα τα dirty στοιχεία συλλέγονται και ανακατασκευάζονται σε ένα καρέ. Αυτό σημαίνει ότι αν το setState κληθεί πολλές φορές εντός του ίδιου σύγχρονου μπλοκ, το build θα εκτελεστεί μόνο μία φορά — μετά την ολοκλήρωση όλων των αλλαγών. Αυτή η βελτιστοποίηση αποτρέπει πολλαπλές ανακατασκευές ανά καρέ.
Σύμφωνα με το Flutter Engine Team (Google, 2025), ο μηχανισμός dirty-σημαιών βασίζεται στη διέλευση του BuildOwner._dirtyElements. Κάθε dirty StatefulElement προστίθεται στη λίστα και επεξεργάζεται στο στάδιο ενημέρωσης καρέ. Αν το widget αφαιρέθηκε από το δέντρο πριν από την επεξεργασία, αποκλείεται αυτόματα από τη λίστα dirty στοιχείων.
Βασικό παράδειγμα setState() με αύξηση μετρητή. Παρουσιάζει σωστή χρήση: αλλαγή πεδίου εντός callback:
class _CounterState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++; // μετάλλαξη πεδίου εντός callback
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('$_count'),
);
}
}
Παράδειγμα με πεδίο κειμένου και ελεγκτή — setState() για διαχείριση ορατότητας κωδικού πρόσβασης:
class _PasswordFieldState extends State<PasswordField> {
bool _obscured = true;
final _controller = TextEditingController();
void _toggleVisibility() {
setState(() {
_obscured = !_obscured;
});
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
obscureText: _obscured,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
onPressed: _toggleVisibility,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Σε αυτό το παράδειγμα, το setState() αλλάζει μόνο το λογικό πεδίο _obscured, προκαλώντας ανακατασκευή του TextField με νέο εικονίδιο και τρόπο εμφάνισης. Ο ελεγκτής κειμένου δεν αναδημιουργείται — αρχικοποιείται μία φορά στο initState και απελευθερώνεται στο dispose.
Αν χρειάζεται να αλλάξετε πολλά πεδία, όλες οι αλλαγές εκτελούνται εντός ενός setState. Αυτό εγγυάται ότι το build θα δει συνεπή κατάσταση:
setState(() {
_isLoading = false;
_items = newItems;
_error = null;
});
Τρία πεδία αλλάζουν σε ένα callback — το build θα εκτελεστεί μία φορά και θα δει όλες τις αλλαγές ταυτόχρονα. Αν κάθε κλήση ήταν ξεχωριστό setState, το build και πάλι θα εκτελούνταν μία φορά χάρη στη μαζική επεξεργασία dirty στοιχείων.
Μία από τις σημαντικότερες αποχρώσεις του setState() — η συμπεριφορά του με ασύγχρονες λειτουργίες. Το callback του setState εκτελείται σύγχρονα, αλλά αν εντός του κληθεί await, ο κώδικας μετά το await θα εκτελεστεί αφού το setState ολοκληρώσει την εργασία του. Αυτό σημαίνει ότι οι αλλαγές πεδίων μετά το await δεν θα συλληφθούν από το τρέχον setState.
Σωστή προσέγγιση: η ασύγχρονη λειτουργία εκτελείται εκτός setState και το setState καλείται μετά την ολοκλήρωσή της. Όλος ο κώδικας μεταξύ λήψης αποτελέσματος και κλήσης setState εκτελείται σε σύγχρονο περιβάλλον μετά το await:
// ΣΩΣΤΟ: await εκτός setState
Future<void> _loadData() async {
final result = await ApiService.fetchData();
setState(() {
_data = result;
_isLoading = false;
});
}
// ΛΑΘΟΣ: await εντός setState — καμία εγγύηση ενημέρωσης
void _loadDataWrong() {
setState(() async {
_data = await ApiService.fetchData(); // το setState επιστρέφει πριν ολοκληρωθεί το await
_isLoading = false; // αυτός ο κώδικας δεν συλλαμβάνεται από το setState
});
}
Σύμφωνα με τα έγγραφα Flutter (Dart async patterns, 2026), η μεταβίβαση async-callback στο setState είναι αντι-πρότυπο, καθώς το setState αναμένει VoidCallback (σύγχρονη συνάρτηση), ενώ μια async συνάρτηση επιστρέφει Future που αγνοείται. Οι αλλαγές μετά το πρώτο await σε τέτοιο callback δεν θα υποστούν σωστή επεξεργασία από το πλαίσιο.
Πριν από την κλήση setState() μετά από ασύγχρονη λειτουργία, ελέγχετε πάντα το mounted:
if (mounted) {
setState(() => _data = data);
}
Αν το widget αφαιρέθηκε από το δέντρο κατά την εκτέλεση της ασύγχρονης λειτουργίας, το mounted θα γίνει false και το setState δεν θα κληθεί. Αυτό αποτρέπει εξαίρεση και διαρροή πόρων.
setState() — ένας βολικός αλλά δυνητικά δαπανηρός μηχανισμός, αν χρησιμοποιείται αλόγιστα. Κάθε κλήση setState ανακατασκευάζει ολόκληρο το widget και όλους τους απογόνους του (αν δεν είναι const). Σε βαθιά δέντρα ή με συχνές κλήσεις, αυτό μπορεί να οδηγήσει σε πτώση FPS.
Κύριες στρατηγικές βελτιστοποίησης: ελαχιστοποίηση περιοχής ανακατασκευής (μεταφορά μεταβλητών τμημάτων UI σε ξεχωριστά StatefulWidget), χρήση const για αμετάβλητους απογόνους και αποφυγή κλήσης setState σε γονικά widgets αν άλλαξε μόνο μια μικρή λεπτομέρεια UX. Αν η κατάσταση ενημερώνεται με υψηλή συχνότητα (κίνηση, ροή δεδομένων), εξετάστε το AnimatedBuilder ή το ValueListenableBuilder.
Σύμφωνα με το Flutter Performance Best Practices (Flutter.dev, Φεβρουάριος 2026), η προφιλοποίηση πραγματικών εφαρμογών δείχνει ότι έως και 40% όλων των κλήσεων setState μπορούν να αντικατασταθούν με const θυγατρικά widgets ή αντιδραστικούς builders (StreamBuilder, FutureBuilder). Αυτό μειώνει τον μέσο χρόνο κατασκευής καρέ κατά 15–25%.
| Σενάριο | Εναλλακτική | Πλεονέκτημα |
|---|---|---|
| Κίνηση | AnimatedBuilder | Ανακατασκευάζει μόνο το κινούμενο widget |
| Ροή δεδομένων | StreamBuilder | Αντιδρά σε κάθε στοιχείο ροής |
| Μελλοντικό αποτέλεσμα | FutureBuilder | Διαχειρίζεται καταστάσεις φόρτωσης/σφάλματος |
| Τοπική τιμή | ValueListenableBuilder | Αντιδρά σε αλλαγή μίας τιμής |
Παρά την ευελιξία του setState(), σε μεγάλα έργα χρησιμοποιείται κυρίως για τοπική κατάσταση. Για καθολική ή κοινόχρηστη κατάσταση, χρησιμοποιούνται εξειδικευμένες λύσεις, καθεμία από τις οποίες αντικαθιστά ή περιτυλίγει το setState.
Το Provider χρησιμοποιεί ChangeNotifier + notifyListeners ως ανάλογο του setState, αλλά με δυνατότητα εγγραφής πολλών widgets. Το Bloc χρησιμοποιεί Streams — η κατάσταση αλλάζει με προσθήκη γεγονότων στο StreamController. Το Riverpod συνδυάζει προσεγγίσεις, προσφέροντας τόσο τοπική (StateProvider) όσο και ασύγχρονη (AsyncNotifier) διαχείριση χωρίς δέσμευση στο StatefulWidget. Και οι τρεις προσεγγίσεις εξαλείφουν την ανάγκη χειροκίνητης κλήσης setState — η ενημέρωση UI γίνεται αυτόματα κατά την αλλαγή δεδομένων.
Σύμφωνα με το Flutter Community Survey 2025 (Flutter Foundation, Δεκέμβριος 2025), το 74% των προγραμματιστών χρησιμοποιεί τουλάχιστον ένα εργαλείο διαχείρισης κατάστασης εκτός από το setState. Παράλληλα, το 92% συνεχίζει να χρησιμοποιεί setState για τοπικά δεδομένα πεδίου κειμένου, checkbox ή απλού μετρητή — αυτό θεωρείται βέλτιστη πρακτική (best practice).
Το πρώτο και πιο επικίνδυνο λάθος — κλήση setState μετά από dispose. Η ασύγχρονη λειτουργία ξεκίνησε στο initState, ο χρήστης έφυγε από την οθόνη, το widget αφαιρέθηκε και το callback της ασύγχρονης λειτουργίας καλεί setState — η εφαρμογή καταρρέει με εξαίρεση. Λύση — ελέγχετε πάντα το mounted πριν από την κλήση.
Δεύτερο λάθος — κλήση setState εντός του build. Αυτό οδηγεί σε ατέλειωτο βρόχο: build → setState → dirty → build → setState → ... Το Flutter δεν μπλοκάρει τέτοια κλήση (θα λάβετε StackOverflowError). Το setState μπορεί να κληθεί μόνο ως απόκριση σε συμβάν (πάτημα κουμπιού, ολοκλήρωση Future, λήψη δεδομένων από ροή).
Τρίτο λάθος — αλλαγή πεδίων State χωρίς κλήση setState. Ο προγραμματιστής γράφει _count++ και περιμένει το UI να ενημερωθεί. Το Flutter δεν μπορεί να παρακολουθεί αυτόματα αλλαγές πεδίων — χρειάζεται ρητό σήμα μέσω setState. Αυτή είναι θεμελιώδης διαφορά από αντιδραστικά πλαίσια όπως το Vue.js, όπου η αλλαγή δεδομένων ενεργοποιεί αυτόματα ενημέρωση.
Τέταρτο λάθος — κλήση setState με ασύγχρονο callback (async-lambda). Όπως περιγράφεται στην ενότητα για τον ασυγχρονισμό, οι αλλαγές μετά το await δεν θα συλληφθούν, οδηγώντας σε σφάλματα δύσκολα αναπαραγώγιμα. Χρησιμοποιήστε σύγχρονο callback και καλέστε setState μετά το await.
mounted σε ασύγχρονα callbacksΣυχνές Ερωτήσεις
setState() ειδοποιεί το Flutter ότι τα εσωτερικά δεδομένα του StatefulWidget άλλαξαν και το UI πρέπει να ανακατασκευαστεί. Η μέθοδος δέχεται ένα callback, το εκτελεί σύγχρονα, επισημαίνει το widget ως dirty και προγραμματίζει κλήση build στο επόμενο καρέ.
Το UI δεν θα ενημερωθεί. Το Flutter δεν παρακολουθεί αυτόματα αλλαγές πεδίων. Η τιμή του πεδίου θα αλλάξει στη μνήμη, αλλά το widget θα παραμείνει στην προηγούμενη κατάσταση μέχρι την επόμενη αναγκαστική ανακατασκευή από τον γονέα.
Δεν μπορεί. Αυτό θα οδηγήσει σε ατέλειωτο βρόχο: το build καλεί setState, που επισημαίνει το widget ως dirty και καλεί ξανά build. Το Flutter δεν μπλοκάρει τέτοια κατάσταση — η εφαρμογή θα καταρρεύσει με StackOverflowError.
Το build θα εκτελεστεί μία φορά. Το Flutter συλλέγει όλα τα dirty στοιχεία και τα ανακατασκευάζει μαζικά στο τέλος του καρέ. Το δεύτερο setState πριν από την επεξεργασία του πρώτου απλώς προσθέτει το στοιχείο στην ίδια λίστα dirty στοιχείων — δεν θα γίνει επαναληπτική ανακατασκευή.
mounted — μια λογική σημαία που δείχνει ότι το widget βρίσκεται ακόμα στο δέντρο. Αν μετά από ασύγχρονη λειτουργία καλέσετε setState χωρίς έλεγχο mounted, και το widget έχει ήδη αφαιρεθεί — η εφαρμογή θα καταρρεύσει με την εξαίρεση „setState called after dispose”.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης