setState() — metoda cheie a clasei State în Flutter, care notifică framework-ul despre modificarea datelor și declanșează reconstruirea interfeței. Conform documentației oficiale Flutter (Flutter.dev, 2026), setState este mecanismul principal de reactivitate în StatefulWidget: fără apelarea sa, UI nu va afla despre modificările câmpurilor State și va rămâne în starea anterioară. Metoda primește un callback VoidCallback, în interiorul căruia dezvoltatorul modifică câmpurile variabile, după care Flutter apelează automat build pentru reconstruirea widget-ului.
Principalele puncte
setState() — o metodă încorporată a clasei State în Flutter, destinată notificării framework-ului că starea internă a widget-ului s-a schimbat și este necesară reconstruirea UI. Fără apelarea setState, Flutter nu știe despre modificări — chiar dacă câmpurile State au fost modificate, interfața va rămâne neschimbată până la următoarea reconstruire forțată de către părinte.
Semnătura metodei: void setState(VoidCallback fn). Callback-ul este executat sincron în interiorul setState și abia după finalizarea sa, State este marcat ca dirty. Aceasta garantează că toate modificările sunt aplicate atomic înainte de reconstruire. Conform Dart Language Specification (Dart Team, 2026), atomicitatea setState previne stările de cursă, în care build ar putea vedea o stare parțial actualizată.
setState nu primește argumente, nu returnează o valoare și nu poate fi suprascris. Este o metodă finală (sealed) a clasei State. Dezvoltatorul nu poate schimba comportamentul său — poate doar să îl utilizeze conform destinației. Încercarea de a apela setState în afara State (de exemplu, din altă clasă) este imposibilă, deoarece metoda este declarată în clasa State.
O concepție greșită comună — să credeți că setState însuși modifică starea. Nu este adevărat. setState doar apelează callback-ul transmis (în care dezvoltatorul modifică câmpurile) și apoi semnalează framework-ului necesitatea build. Callback-ul este obligatoriu — transmiterea null sau a unui callback gol va duce la o eroare.
Mecanismul de funcționare al setState() poate fi împărțit în patru etape. Prima — apelarea metodei cu callback. A doua — executarea sincronă a callback-ului, în interiorul căruia se modifică câmpurile State. A treia — State este marcat ca dirty în câmpul special _dirty. A patra — la sfârșitul microtask-ului curent, Flutter parcurge toate elementele dirty și le apelează build în ordinea apariției în arbore.
Un detaliu important: setState nu apelează build imediat. Flutter utilizează o strategie de actualizare în lot: toate elementele dirty sunt colectate și reconstruite într-un singur cadru. Aceasta înseamnă că, dacă setState este apelat de mai multe ori în cadrul aceluiași bloc sincron, build se va executa o singură dată — după finalizarea tuturor modificărilor. Această optimizare previne reconstruirile multiple într-un cadru.
Potrivit Flutter Engine Team (Google, 2025), mecanismul flag-urilor dirty se bazează pe traversarea BuildOwner._dirtyElements. Fiecare StatefulElement dirty este adăugat la listă și procesat în etapa de actualizare a cadrului. Dacă widget-ul a fost eliminat din arbore înainte de procesare, este automat exclus din lista elementelor dirty.
Exemplul de bază setState() cu incrementarea contorului. Demonstrează utilizarea corectă: modificarea câmpului în interiorul callback-ului:
class _CounterState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++; // mutarea câmpului în interiorul callback-ului
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('$_count'),
);
}
}
Exemplu cu câmp text și controller — setState() pentru gestionarea vizibilității parolei:
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();
}
}
În acest exemplu, setState() modifică doar câmpul boolean _obscured, ceea ce provoacă reconstruirea TextField cu o nouă pictogramă și mod de afișare. Controller-ul text nu este recreat — este inițializat o dată în initState și eliberat în dispose.
Dacă trebuie să modificați mai multe câmpuri, toate modificările se execută în interiorul unui singur setState. Aceasta garantează că build va vedea o stare consistentă:
setState(() {
_isLoading = false;
_items = newItems;
_error = null;
});
Trei câmpuri sunt modificate într-un singur callback — build se va executa o dată și va vedea toate modificările simultan. Dacă fiecare apel ar fi un setState separat, build s-ar executa tot o singură dată datorită procesării în lot a elementelor dirty.
Unul dintre cele mai importante nuanțe ale setState() — comportamentul său cu operațiile asincrone. Callback-ul setState este executat sincron, dar dacă în interiorul său este apelat await, codul după await se va executa după ce setState și-a încheiat activitatea. Aceasta înseamnă că modificările câmpurilor după await nu vor fi capturate de setState-ul curent.
Abordarea corectă: operația asincronă se execută în afara setState, iar setState este apelat după finalizarea sa. Tot codul dintre primirea rezultatului și apelarea setState se execută în context sincron după await:
// CORECT: await în afara setState
Future<void> _loadData() async {
final result = await ApiService.fetchData();
setState(() {
_data = result;
_isLoading = false;
});
}
// GREȘIT: await în interiorul setState — nicio garanție de actualizare
void _loadDataWrong() {
setState(() async {
_data = await ApiService.fetchData(); // setState se întoarce înainte ca await să se finalizeze
_isLoading = false; // acest cod nu este capturat de setState
});
}
Conform documentației Flutter (Dart async patterns, 2026), transmiterea unui callback async către setState este un antipattern, deoarece setState așteaptă VoidCallback (o funcție sincronă), iar o funcție async returnează un Future care este ignorat. Modificările după primul await într-un astfel de callback nu vor fi procesate corect de framework.
Înainte de a apela setState() după o operație asincronă, verificați întotdeauna mounted:
if (mounted) {
setState(() => _data = data);
}
Dacă widget-ul a fost eliminat din arbore în timpul executării operației asincrone, mounted va deveni false și setState nu va fi apelat. Aceasta previne excepțiile și scurgerile de resurse.
setState() — un mecanism convenabil, dar potențial costisitor, dacă este utilizat fără gândire. Fiecare apel setState reconstruiește întregul widget și toți descendenții săi (dacă nu sunt const). În arbori adânci sau la apeluri frecvente, aceasta poate duce la scăderi de FPS.
Principalele strategii de optimizare: minimizarea zonei de reconstruire (extragerea părților variabile ale UI în widget-uri StatefulWidget separate), utilizarea const pentru descendenții nemodificabili și evitarea apelării setState în widget-urile părinte dacă s-a modificat doar un mic detaliu UX. Dacă starea este actualizată cu frecvență ridicată (animație, flux de date), luați în considerare AnimatedBuilder sau ValueListenableBuilder.
Conform Flutter Performance Best Practices (Flutter.dev, februarie 2026), profilarea aplicațiilor reale arată că până la 40% din toate apelurile setState pot fi înlocuite cu widget-uri copil const sau cu builder-i reactivi (StreamBuilder, FutureBuilder). Aceasta reduce timpul mediu de construire a cadrului cu 15–25%.
| Scenariu | Alternativă | Avantaj |
|---|---|---|
| Animație | AnimatedBuilder | Reconstruiește doar widget-ul animat |
| Flux de date | StreamBuilder | Reacționează la fiecare element al fluxului |
| Rezultat viitor | FutureBuilder | Gestionează stările de încărcare/eroare |
| Valoare locală | ValueListenableBuilder | Reacționează la modificarea unei singure valori |
În ciuda versatilității setState(), în proiectele mari este utilizat în principal pentru starea locală. Pentru starea globală sau partajată, se folosesc soluții specializate, fiecare dintre acestea înlocuind sau împachetând setState.
Provider utilizează ChangeNotifier + notifyListeners ca analog al setState, dar cu posibilitatea de abonare a mai multor widget-uri. Bloc utilizează Streams — starea se modifică prin adăugarea de evenimente în StreamController. Riverpod combină abordările, oferind atât gestionare locală (StateProvider), cât și asincronă (AsyncNotifier) fără legare de StatefulWidget. Toate trei abordări elimină necesitatea de a apela manual setState — actualizarea UI are loc automat la modificarea datelor.
Conform Flutter Community Survey 2025 (Flutter Foundation, decembrie 2025), 74% dintre dezvoltatori folosesc cel puțin un instrument de gestionare a stării în plus față de setState. În același timp, 92% continuă să folosească setState pentru datele locale ale câmpului text, checkbox-ului sau contorului simplu — acesta este considerat best practice.
Prima și cea mai periculoasă eroare — apelarea setState după dispose. Operația asincronă a început în initState, utilizatorul a părăsit ecranul, widget-ul a fost eliminat, iar callback-ul operației asincrone apelează setState — aplicația se prăbușește cu o excepție. Soluția — verificați întotdeauna mounted înainte de apelare.
A doua eroare — apelarea setState în interiorul build. Aceasta duce la o buclă infinită: build → setState → dirty → build → setState → ... Flutter nu blochează o astfel de apelare (veți primi StackOverflowError). setState poate fi apelat doar ca răspuns la un eveniment (apăsarea unui buton, finalizarea Future, primirea de date dintr-un flux).
A treia eroare — modificarea câmpurilor State fără apelarea setState. Dezvoltatorul scrie _count++ și se așteaptă ca UI să se actualizeze. Flutter nu poate urmări modificările câmpurilor automat — are nevoie de un semnal explicit prin setState. Aceasta este o diferență fundamentală față de framework-urile reactive precum Vue.js, unde modificarea datelor declanșează automat actualizarea.
A patra eroare — apelarea setState cu un callback asincron (async-lambda). După cum este descris în secțiunea despre asincronism, modificările după await nu vor fi capturate, ceea ce duce la bug-uri greu de reprodus. Utilizați un callback sincron și apelați setState după await.
mounted în callback-urile asincroneÎntrebări frecvente
setState() notifică Flutter că datele interne ale StatefulWidget s-au schimbat și UI trebuie reconstruit. Metoda primește un callback, îl execută sincron, marchează widget-ul ca dirty și planifică apelarea build în cadrul următor.
UI nu se va actualiza. Flutter nu urmărește modificările câmpurilor automat. Valoarea câmpului se va schimba în memorie, dar widget-ul va rămâne în starea anterioară până la următoarea reconstruire forțată de către părinte.
Nu se poate. Aceasta va duce la o buclă infinită: build apelează setState, care marchează widget-ul ca dirty și apelează din nou build. Flutter nu blochează o astfel de situație — aplicația se va prăbuși cu StackOverflowError.
Build se va executa o singură dată. Flutter colectează toate elementele dirty și le reconstruiește în lot la sfârșitul cadrului. Al doilea setState înainte de procesarea primului adaugă pur și simplu elementul în aceeași listă de elemente dirty — nu va avea loc o reconstruire repetată.
mounted — un indicator boolean care arată că widget-ul se află încă în arbore. Dacă după o operație asincronă apelați setState fără a verifica mounted, iar widget-ul a fost deja eliminat — aplicația se va prăbuși cu excepția „setState called after dispose”.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și