setState() è il metodo chiave di State in Flutter, che notifica al framework i cambiamenti dei dati e attiva la ricostruzione dell'interfaccia. Secondo la documentazione ufficiale di Flutter (Flutter.dev, 2026), setState è il meccanismo principale di reattività in StatefulWidget: senza la sua chiamata, l'UI non viene a conoscenza delle modifiche ai campi di State e rimane nello stato precedente. Il metodo accetta un VoidCallback, all'interno del quale lo sviluppatore modifica i campi mutabili, dopo di che Flutter chiama automaticamente build per ricostruire il widget.
Punti chiave
setState() è un metodo incorporato della classe State in Flutter, progettato per notificare al framework che lo stato interno del widget è cambiato e l'UI deve essere ricostruita. Senza chiamare setState, Flutter non viene a conoscenza delle modifiche — anche se i campi di State sono stati modificati, l'interfaccia rimarrà invariata fino alla prossima ricostruzione forzata dal genitore.
Firma del metodo: void setState(VoidCallback fn). Il callback viene eseguito in modo sincrono all'interno di setState, e solo dopo il suo completamento State viene marcato come sporco. Questo garantisce che tutte le modifiche siano applicate atomicamente prima della ricostruzione. Secondo la Specifica del Linguaggio Dart (Dart Team, 2026), l'atomicità di setState previene le condizioni di gara in cui build potrebbe vedere uno stato parzialmente aggiornato.
setState non accetta argomenti, non restituisce valori e non può essere sovrascritto. È un metodo finale (sealed) della classe State. Lo sviluppatore non può cambiarne il comportamento — solo usarlo come previsto. Tentare di chiamare setState al di fuori di State (ad esempio, da un'altra classe) è impossibile perché il metodo è dichiarato nella classe State.
Un equivoco comune è pensare che setState cambi lo stato da solo. Non è vero. setState chiama solo il callback passato (in cui lo sviluppatore modifica i campi) e poi segnala al framework la necessità di build. Il callback è obbligatorio — passare null o un callback vuoto causerà un errore.
Il meccanismo di funzionamento di setState() può essere suddiviso in quattro fasi. Prima — chiamata del metodo con un callback. Seconda — esecuzione sincrona del callback, all'interno del quale vengono modificati i campi di State. Terza — State viene marcato come sporco in un campo speciale _dirty. Quarta — alla fine del microtask corrente, Flutter itera attraverso tutti gli elementi sporchi e chiama il loro build nell'ordine di apparizione nell'albero.
Un dettaglio importante: setState non chiama build immediatamente. Flutter usa una strategia di aggiornamento batch: tutti gli elementi sporchi vengono raccolti e ricostruiti in un unico fotogramma. Ciò significa che se setState viene chiamato più volte all'interno di un singolo blocco sincrono, build verrà eseguito solo una volta — dopo che tutte le modifiche sono state completate. Questa ottimizzazione previene molteplici ricostruzioni per fotogramma.
Secondo il Flutter Engine Team (Google, 2025), il meccanismo del flag sporco si basa sul passaggio BuildOwner._dirtyElements. Ogni StatefulElement sporco viene aggiunto alla lista ed elaborato nella fase di aggiornamento del fotogramma. Se un widget è stato rimosso dall'albero prima dell'elaborazione, viene automaticamente escluso dalla lista degli elementi sporchi.
Esempio base di setState() con incremento di contatore. Mostra l'uso corretto: modifica di un campo all'interno del callback:
class _CounterState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++; // modifica del campo all'interno del callback
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('$_count'),
);
}
}
Esempio con campo di testo e controller — setState() per la gestione della visibilità della password:
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();
}
}
In questo esempio, setState() modifica solo il campo booleano _obscured, che attiva la ricostruzione del TextField con una nuova icona e modalità di visualizzazione. Il controller di testo non viene ricreato — viene inizializzato una volta in initState e rilasciato in dispose.
Se devi modificare più campi, tutte le modifiche dovrebbero essere effettuate all'interno di un unico setState. Questo garantisce che build vedrà uno stato consistente:
setState(() {
_isLoading = false;
_items = newItems;
_error = null;
});
Tre campi vengono modificati in un callback — build verrà eseguito una volta e vedrà tutte le modifiche simultaneamente. Se ogni chiamata fosse un setState separato, build verrebbe comunque eseguito solo una volta grazie all'elaborazione batch degli elementi sporchi.
Una delle sfumature più importanti di setState() è il suo comportamento con le operazioni asincrone. Il callback di setState viene eseguito in modo sincrono, ma se al suo interno viene chiamato await, il codice dopo await verrà eseguito dopo che setState ha già completato il suo lavoro. Ciò significa che le modifiche ai campi dopo await non verranno catturate dal setState corrente.
L'approccio corretto: l'operazione asincrona viene eseguita al di fuori di setState, e setState viene chiamato dopo il suo completamento. Tutto il codice tra la ricezione del risultato e la chiamata a setState viene eseguito in un contesto sincrono dopo await:
// CORRETTO: await fuori da setState
Future<void> _loadData() async {
final result = await ApiService.fetchData();
setState(() {
_data = result;
_isLoading = false;
});
}
// SBAGLIATO: await dentro setState — nessuna garanzia di aggiornamento
void _loadDataWrong() {
setState(() async {
_data = await ApiService.fetchData(); // setState ritorna prima del completamento di await
_isLoading = false; // questo codice non viene catturato da setState
});
}
Secondo la documentazione Flutter (Dart async patterns, 2026), passare un callback asincrono a setState è un anti-pattern perché setState si aspetta un VoidCallback (funzione sincrona), mentre una funzione asincrona restituisce un Future che viene ignorato. Le modifiche dopo il primo await in tale callback non verranno gestite correttamente dal framework.
Prima di chiamare setState() dopo un'operazione asincrona, controlla sempre mounted:
if (mounted) {
setState(() => _data = data);
}
Se il widget è stato rimosso dall'albero durante l'operazione asincrona, mounted diventerà false e setState non verrà chiamato. Questo previene eccezioni e perdite di risorse.
setState() è un meccanismo comodo ma potenzialmente costoso se usato sconsideratamente. Ogni chiamata a setState ricostruisce l'intero widget e tutti i suoi discendenti (se non sono const). In alberi profondi o con chiamate frequenti, questo può portare a cali di FPS.
Principali strategie di ottimizzazione: minimizzare l'area di ricostruzione (estrarre parti mutabili dell'UI in StatefulWidget separati), usare const per figli immutabili ed evitare di chiamare setState in widget genitori se è cambiato solo un piccolo dettaglio UX. Se lo stato viene aggiornato ad alta frequenza (animazione, flusso di dati), considera AnimatedBuilder o ValueListenableBuilder.
Secondo le Best Practice per le Prestazioni Flutter (Flutter.dev, febbraio 2026), la profilazione di applicazioni reali mostra che fino al 40% di tutte le chiamate a setState possono essere sostituite con widget figli const o costruttori reattivi (StreamBuilder, FutureBuilder). Questo riduce il tempo medio di costruzione del fotogramma del 15–25%.
| Scenario | Alternativa | Vantaggio |
|---|---|---|
| Animazione | AnimatedBuilder | Ricostruisce solo il widget animato |
| Flusso di dati | StreamBuilder | Reagisce a ogni elemento del flusso |
| Risultato futuro | FutureBuilder | Gestisce stati di caricamento/errore |
| Valore locale | ValueListenableBuilder | Reagisce a modifiche di un singolo valore |
Nonostante la versatilità di setState(), nei grandi progetti viene utilizzato principalmente per lo stato locale. Per lo stato globale o condiviso, vengono utilizzate soluzioni specializzate, ciascuna delle quali sostituisce o incapsula setState.
Provider usa ChangeNotifier + notifyListeners come analogo di setState, ma con la possibilità di sottoscrivere più widget. Bloc usa Streams — lo stato viene modificato aggiungendo eventi a uno StreamController. Riverpod combina approcci, fornendo sia gestione locale (StateProvider) che asincrona (AsyncNotifier) senza legame a StatefulWidget. Tutti e tre gli approcci eliminano la necessità di chiamare manualmente setState — gli aggiornamenti dell'UI avvengono automaticamente quando i dati cambiano.
Secondo il Sondaggio della Community Flutter 2025 (Flutter Foundation, dicembre 2025), il 74% degli sviluppatori utilizza almeno uno strumento di gestione dello stato oltre a setState. Allo stesso tempo, il 92% continua a usare setState per dati locali di campi di testo, caselle di controllo o semplici contatori — questo è considerato una best practice.
Il primo e più pericoloso errore è chiamare setState dopo dispose. Un'operazione asincrona avviata in initState, l'utente ha lasciato lo schermo, il widget è stato rimosso e il callback asincrono chiama setState — l'applicazione crasha con un'eccezione. Soluzione — controlla sempre mounted prima di chiamare.
Il secondo errore è chiamare setState all'interno di build. Questo porta a un ciclo infinito: build → setState → sporco → build → setState → ... Flutter non blocca tale chiamata (otterrai un StackOverflowError). setState può essere chiamato solo in risposta a un evento (pressione di un pulsante, completamento di Future, dati da un flusso).
Il terzo errore è modificare i campi di State senza chiamare setState. Lo sviluppatore scrive _count++ e si aspetta che l'UI si aggiorni. Flutter non può tracciare automaticamente le modifiche ai campi — ha bisogno di un segnale esplicito tramite setState. Questa è una differenza fondamentale dai framework reattivi come Vue.js, dove le modifiche ai dati attivano automaticamente gli aggiornamenti.
Il quarto errore è chiamare setState con un callback asincrono (lambda async). Come descritto nella sezione sull'asincronia, le modifiche dopo await non verranno catturate, portando a bug difficili da riprodurre. Usa un callback sincrono e chiama setState dopo await.
mounted nei callback asincroniDomande frequenti
setState() notifica Flutter che i dati interni di StatefulWidget sono cambiati e l'UI deve essere ricostruita. Il metodo accetta un callback, lo esegue in modo sincrono, marca il widget come sporco e pianifica la chiamata a build nel fotogramma successivo.
L'UI non si aggiornerà. Flutter non traccia automaticamente le modifiche ai campi. Il valore del campo cambia in memoria, ma il widget rimane nello stato precedente fino alla prossima ricostruzione forzata dal genitore.
No. Questo porta a un ciclo infinito: build chiama setState, che marca il widget come sporco e chiama di nuovo build. Flutter non blocca questa situazione — l'applicazione crasha con StackOverflowError.
Build verrà eseguito una volta. Flutter raccoglie tutti gli elementi sporchi e li ricostruisce in batch alla fine del fotogramma. Il secondo setState prima dell'elaborazione aggiunge semplicemente l'elemento alla stessa lista di elementi sporchi — non si verifica alcuna ricostruzione ripetuta.
mounted è un flag booleano che indica che il widget è ancora nell'albero. Se setState viene chiamato dopo un'operazione asincrona senza controllare mounted e il widget è già stato rimosso — l'applicazione crasha con l'eccezione “setState called after dispose”.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche