StatefulWidget è un widget Flutter con stato mutabile, che consente all'interfaccia utente di reagire alle azioni dell'utente, agli eventi asincroni e ai flussi di dati. Secondo la documentazione ufficiale di Flutter (Flutter.dev, 2026), StatefulWidget viene utilizzato per tutti gli elementi interattivi dell'applicazione: moduli di input, animazioni, caselle di controllo, interruttori e schermate che caricano dati dalla rete. A differenza di StatelessWidget, crea un oggetto State separato che persiste per tutto il suo ciclo di vita e può essere ricostruito senza ricreare il widget stesso.
Punti chiave
StatefulWidget è una classe Flutter che può modificare il suo stato in risposta alle azioni dell'utente, agli eventi di sistema o alle operazioni asincrone. A differenza di StatelessWidget, StatefulWidget non viene renderizzato direttamente — crea un oggetto State che si occupa del rendering. Questa separazione in due classi (Widget e State) consente a Flutter di ricostruire l'interfaccia senza ricreare il widget stesso, offrendo un vantaggio significativo in termini di prestazioni durante gli aggiornamenti frequenti.
L'architettura di StatefulWidget segue il pattern di “separazione tra mutabile e immutabile”: il widget stesso rimane immutabile (come StatelessWidget), mentre tutto lo stato mutabile viene memorizzato in un oggetto State separato. Ciò consente a Flutter di riutilizzare i widget confrontandoli per tipo e Key, preservando allo stesso tempo lo stato effettivo tra le ricostruzioni.
Secondo Google (Flutter Architectural Overview, 2026), StatefulWidget è ottimale per scenari in cui lo stato cambia più di una volta durante la vita del widget: campi di testo, animazioni, timer, flussi di dati, caricamenti asincroni. Per l'inizializzazione una tantum, StatelessWidget è sufficiente.
StatefulWidget è obbligatorio quando il widget deve rispondere a eventi esterni: clic su pulsanti, completamento di richieste HTTP, aggiornamenti di dati del database, abbonamenti WebSocket. È necessario anche per widget con animazioni, campi di testo con controller e componenti che gestiscono il focus. Se un widget visualizza solo dati e non genera eventi, usa StatelessWidget.
StatefulWidget è composto da due classi: il StatefulWidget stesso (leggero, immutabile) e State (pesante, mutabile). Il framework crea State tramite il metodo createState(), chiamato una volta quando viene inserito nell'albero. State riceve un riferimento al widget tramite la proprietà widget e può accedere ai suoi campi in qualsiasi momento del ciclo di vita.
Il ciclo di vita di StatefulWidget comprende sei fasi principali, ognuna delle quali fornisce un metodo sovrascrivibile per eseguire compiti specifici. Comprendere queste fasi è fondamentale per una corretta gestione delle risorse e per evitare perdite di memoria.
createState è il primo metodo del ciclo di vita, chiamato quando StatefulWidget viene inserito nell'albero. Deve restituire una nuova istanza di State associata a questo widget. Questo metodo viene chiamato esattamente una volta durante l'intera vita dell'elemento. È importante non eseguire operazioni pesanti qui — createState dovrebbe essere il più leggero possibile.
initState viene chiamato immediatamente dopo la creazione di State, prima della prima costruzione dell'interfaccia. Qui si esegue: inizializzazione dei controller (TextEditingController, AnimationController), abbonamento ai flussi di dati (StreamSubscription), configurazione dei timer e inizializzazione dei campi. Secondo la documentazione di Flutter (Flutter.dev, 2026), non è possibile chiamare BuildContext.of() in initState — l'albero non è ancora completamente montato.
didChangeDependencies viene chiamato dopo initState e ogni volta che le dipendenze di InheritedWidget cambiano. Questo è un luogo adatto per chiamare MediaQuery.of(context) o abbonarsi a Theme — valori che possono cambiare durante l'esecuzione dell'applicazione. Se un widget usa InheritedWidget, la logica di inizializzazione dovrebbe essere qui, non in initState.
build è il metodo principale che restituisce l'albero dei widget. Viene chiamato dopo initState, dopo didChangeDependencies e dopo ogni setState. didUpdateWidget viene chiamato quando il genitore si ricostruisce e passa un StatefulWidget con nuovi parametri. Qui è possibile confrontare i campi vecchi e nuovi del widget e, se necessario, aggiornare lo stato.
dispose è la fase finale del ciclo di vita. Qui tutte le risorse vengono liberate: gli abbonamenti ai flussi vengono cancellati, i controller vengono rimossi, i timer vengono annullati. Non chiamare dispose porta a perdite di memoria. Dopo dispose, State è considerato morto — chiamare setState al suo interno genera un'eccezione.
Il meccanismo di funzionamento di StatefulWidget si basa sul lavoro coordinato di tre entità: Widget (descrizione leggera), Element (livello intermedio) e State (archiviazione dati). Quando Flutter incontra un StatefulWidget nella descrizione, crea un StatefulElement, che chiama createState e memorizza un riferimento all'oggetto State. Quando il genitore si ricostruisce, Flutter confronta il nuovo widget con l'Element corrente — se il tipo e la Key coincidono, l'Element viene aggiornato e State rimane lo stesso.
Lo stato viene modificato solo tramite la chiamata setState, che notifica al framework la necessità di una ricostruzione. È importante capire: setState non modifica automaticamente lo stato — segna solo il widget come “sporco”. Lo sviluppatore aggiorna autonomamente i campi di State nel callback passato a setState. Dopo il completamento del callback, Flutter chiama build e aggiorna l'interfaccia.
Secondo il team Dart/Flutter (Dart Language Specification, 2026), questa separazione garantisce che tutte le modifiche allo stato avvengano in modo sincrono prima della chiamata a build, eliminando la situazione in cui l'interfaccia mostra dati parzialmente aggiornati. Questo è un meccanismo chiave di coerenza dell'interfaccia in Flutter.
Consideriamo un semplice StatefulWidget — un contatore di clic su pulsante. Mostra il pattern di base: creazione di State, inizializzazione di un campo in initState, modifica tramite setState:
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Incrementa'),
),
],
);
}
}
Un esempio con caricamento asincrono dei dati e gestione del ciclo di vita. StatefulWidget carica dati dalla rete e mostra lo stato di caricamento:
class UserProfilePage extends StatefulWidget {
final String userId;
const UserProfilePage({super.key, required this.userId});
@override
State<UserProfilePage> createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
UserModel? _user;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await UserService.fetchUser(widget.userId);
setState(() {
_user = user;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
if (_isLoading) return const CircularProgressIndicator();
return Text('Ciao, ${_user!.name}');
}
}
Nel secondo esempio, è importante notare: initState avvia un'operazione asincrona, ma il metodo stesso non è asincrono. L'asincronicità viene implementata tramite async/await all'interno di un metodo separato _loadUser, che aggiorna lo stato tramite setState dopo il completamento della richiesta. Questo approccio garantisce che il widget visualizzi correttamente l'indicatore di caricamento prima di ricevere i dati.
La scelta tra StatefulWidget e StatelessWidget non riguarda solo la presenza dello stato. StatefulWidget fornisce un ciclo di vita completo con i metodi initState, didChangeDependencies, didUpdateWidget e dispose, necessari per lavorare con controller, animazioni e flussi. StatelessWidget, d'altra parte, non ha questi metodi ed è sempre più leggero per il framework.
La raccomandazione del team Flutter (Flutter docs, 2026) è di ridurre al minimo il numero di StatefulWidget in un'applicazione, sollevando lo stato verso l'alto nell'albero (State Hoisting) o utilizzando soluzioni di gestione dello stato (Riverpod, Bloc, Provider). Ogni StatefulWidget crea un oggetto State che vive fino alla rimozione dell'elemento — più sono questi widget, maggiore è il carico di memoria.
| Criterio | StatefulWidget | StatelessWidget |
|---|---|---|
| Stato | Mutabile | Immutabile |
| Ciclo di vita | 6 fasi | Solo build |
| Oggetto State | Creato separatamente | Non necessario |
| setState | Disponibile | Non disponibile |
| Abbonamenti | initState/dispose | Non supportati |
| Costruttore const | Limitato | Completamente supportato |
| Consumo di memoria | Maggiore | Minore |
StatefulWidget richiede più risorse di StatelessWidget a causa della necessità di creare e mantenere un oggetto State. Tuttavia, l'uso corretto di StatefulWidget non causa problemi di prestazioni se si seguono alcune regole. Primo, evita l'annidamento profondo di StatefulWidget — ogni livello aggiunge overhead all'attraversamento dell'albero. Secondo, dividi un StatefulWidget complesso in diversi widget semplici, ognuno responsabile della propria parte di stato.
Secondo la ricerca sulle prestazioni di Flutter (Flutter.dev, febbraio 2026), la causa più comune di cali di FPS è chiamare setState in un widget genitore che ricostruisce tutti i discendenti, inclusi StatelessWidget che non hanno modificato la loro visualizzazione. La soluzione è estrarre la parte mutabile dell'interfaccia in un StatefulWidget separato in modo che setState ricostruisca solo i widget minimamente necessari.
Usare const all'interno di State è un'altra tecnica importante. Se i widget figli vengono dichiarati come const, Flutter non li ricostruirà quando setState viene chiamato nel genitore. Ciò riduce il carico sul framework e diminuisce il tempo di rendering del frame.
Ogni chiamata setState attiva una ricostruzione completa del widget. Se lo stato cambia con alta frequenza (ad esempio, animazione o flusso di dati), considera l'utilizzo di AnimatedBuilder, ValueListenableBuilder o StreamBuilder invece di chiamare setState manualmente. Questi widget ottimizzano la ricostruzione, aggiornando solo la parte dell'interfaccia che è effettivamente cambiata.
Il primo errore comune con StatefulWidget è chiamare setState dopo dispose. Quando un widget viene rimosso dall'albero, State è considerato morto e qualsiasi chiamata a setState genera un'eccezione “setState called after dispose”. Ciò accade più spesso quando un'operazione asincrona termina dopo la rimozione del widget. La soluzione è verificare il flag mounted prima di chiamare setState o annullare le operazioni asincrone in dispose.
Il secondo errore è eseguire calcoli pesanti nel metodo build. Poiché build viene chiamato ad ogni setState e ad ogni ricostruzione del genitore, tutti i calcoli dovrebbero essere il più leggeri possibile. Se è necessaria un'operazione intensiva in termini di risorse, spostala in un Isolate separato o memorizza nella cache il risultato in un campo di State.
Il terzo errore è non chiamare super.initState() e super.dispose(). Quando si sovrascrivono questi metodi, lo sviluppatore deve chiamare l'implementazione del genitore. In caso contrario, il framework non sarà in grado di gestire correttamente lo stato di Element, portando a bug difficili da tracciare.
mounted prima di setState nei callback asincronisuper.initState() e super.dispose()Domande frequenti
StatefulWidget può modificare il suo stato tramite setState, ha un ciclo di vita (initState, dispose) e crea un oggetto State separato. StatelessWidget non può modificare lo stato e non ha metodi di ciclo di vita — visualizza semplicemente i dati ricevuti.
createState viene chiamato esattamente una volta per ogni istanza di StatefulElement. Anche se il genitore si ricostruisce più volte, finché il tipo e la Key del widget non cambiano, createState non viene chiamato — viene utilizzato l'oggetto State esistente.
Le risorse non verranno liberate: i controller continueranno a funzionare in background, gli abbonamenti ai flussi rimarranno attivi, i timer non verranno annullati. Ciò porta a perdite di memoria e può causare chiamate setState dopo dispose, che generano un'eccezione.
Sì, il costruttore di StatefulWidget può essere const. Tuttavia, ciò non offre lo stesso vantaggio di StatelessWidget — l'oggetto State verrà comunque creato al primo inserimento. const influisce solo sul widget stesso (l'involucro leggero), non sullo State.
didUpdateWidget viene chiamato quando il genitore passa un StatefulWidget con nuovi parametri. Ciò è necessario per sincronizzare lo stato con i nuovi dati — ad esempio, se userId nei parametri è cambiato, è necessario caricare il profilo del nuovo utente.
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