In una lezione precedente abbiamo parlato di Stateless e Stateful Widget come delle fondamenta di un’applicazione Flutter. In particolare, nel corso di queste lezioni ci siamo concentrati e abbiamo preso confidenza proprio con gli stateless widget, che sono perfetti per la creazione di componenti le cui proprietà sono immutabili. Infatti, l’unico modo per modificare questi widget, è la creazione di una nuova istanza.
Un’applicazione, però, non è mai del tutto statica e richiede comunque di:
- tracciare i cambiamenti;
- dare l’opportunità all’utente di poter interagire con la UI;
- aggiornare la UI dinamicamente in caso di cambiamenti nelle informazioni mostrate.
Tutti aspetti, questi, che uno stateless widget non gestisce, ed è proprio in questo contesto che possono essere utilizzati gli stateful widget.
Questo tipo di widget fornisce sia delle configurazioni immutabili (come gli stateless widget) sia un oggetto che ne rappresenta lo stato e che può cambiare nel tempo, rappresentati dalle classi StatefulWidget
e State
rispettivamente. In particolare lo scopo dello State
è quello di:
- rappresentare lo stato dello stateful widget;
- gestire le informazioni per interagire con questo in termini di comportamento e layout.
L’utilizzo di queste due classi è fondamentale a livello di performance in quanto, se da una parte abbiamo che gli StatefulWidget
vengono distrutti e ricostruiti ad ogni cambio nelle configurazioni per apportare le modifiche alla UI, gli State
widget, al contrario, continuano ad esistere e gestiscono le informazioni circa lo stato in cui si trova il widget. Questo comportamento porta ad importanti benefici a livello di performance e di costi computazionali.
In questa e nella prossima lezione vedremo in dettaglio le classi StatefulWidget
e State
e impareremo a creare uno nuovo stateful widget.
Le classi StatefulWidget
e State
Vediamo a grandi linee i metodi e le proprietà offerte da queste classi entrando nel dettaglio del funzionamento del ciclo di vita dello State
.
La classe StatefulWidget
ha lo scopo di:
- descrivere una porzione dell’UI mediante la definizione di altri widget in modo ricorsivo;
- rilevare modifiche all’interfaccia a seguito di un’interazione.
Ad ogni modo, l’istanza stessa del widget è immutabile ma, a differenza della sua controparte stateless, gestisce uno stato mutabile rappresentato dalla classe State
che resterà sempre lo stesso nonostante eventuali cambi di posizione nella struttura.
Di particolare interesse per noi sono i metodi offerti da questa classe, ossia:
Metodo | Descrizione |
---|---|
createElement |
crea un nuovo StatefulElement nell’element tree per gestire la posizione corrente dello StatefulWidget nel widget tree. Questo metodo generalmente non viene mai sovrascritto da una sottoclasse che estende StatefulWidget |
createState |
crea lo stato mutabile (rappresentato da un nuovo oggetto |
La classe State
gioca un ruolo fondamentale nella creazione degli stateful widget in quanto sono mutabili e riutilizzabili. In particolare un oggetto State
:
- può essere letto in modo asincrono;
- potrebbe cambiare durante il ciclo di vita del widget a cui è associato;
- deve essere avvisato quando avviene una modifica attraverso l’utilizzo del metodo
State.setState
.
Vediamo quindi le sue tre proprietà fondamentali e capiamone il funzionamento.
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
context |
BuildContext |
rappresenta la posizione nel widget tree in cui viene creato il widget associato allo stato. In particolare, il framework assocerà in modo permanente il BuildContext allo State dopo l’invocazione del metodo StatefulWidget.createState e prima dell’invocazione dell’initSate |
mounted |
bool |
attraverso questo valore booleano il framework è in grado di sapere se lo State è montato o meno. Lo State risulta montato quando ha associato un BuildContext e resterà in questo stato finché non verrà invocato il metodo dispose |
widget |
T |
rappresenta l'attuale configurazione dello State , ossia l'istanza dello StatefulWidget corrispondente. Questa proprietà è inizializzata dal framework prima di chiamare initState . Se viene aggiornata la configurazione dello stateful widget, verrà creato un nuovo widget con lo stesso runtimeType e Widget.key dello stateful widget corrente.Il nuovo widget verrà quindi impostato come valore di questa proprietà attraverso l’invocazione del metodo didUpdateWidget avente in input la vecchia istanza |
Concentriamoci adesso sui metodi offerti da questa classe che ne definiscono il ciclo di vita.
Metodo | Descrizione |
---|---|
build(BuildContext context) |
descrive la parte di UI rappresentata dallo stateful widget e quindi si occupa di creare l’interfaccia utente a differenza di quanto visto e fatto con uno stateless widget. Questo metodo può essere invocato dal framework:
|
deactivate() |
viene invocato dal framework quando lo State deve essere rimosso dall’element tree a cui è associato |
didChangeDependencies() |
viene invocato quando viene modificata una dipendenza dello State |
didUpdateWidget(covariant T oldWidget) |
viene invocato ogni volta che cambiano le configurazioni del widget. In particolare, quando il widget padre viene ricostruito e richiede la ricreazione dello stateful widget (nella medesima posizione nell’albero e con lo stesso runtimeType e Widget.key ), il framework aggiornerà la proprietà State.widget aggiornando il riferimento al nuovo widget creato |
dispose() |
è invocato quando lo State deve essere rimosso in modo permanente. Una volta invocato, lo State è considerato smontato dal framework e non potrà più essere rimontato. In questo caso la proprietà mounted viene impostata a false |
initState() |
viene invocato quando lo State viene inserito nel widget tree. In particolare, il framework invocherà questo metodo esattamente una volta per ogni State che deve creare |
setState(VoidCallback fn) |
notifica il framework che lo stato interno dello State è cambiato |
Vediamo come vengono impiegati questi metodi dal framework per gestire il ciclo di vita di un oggetto State
e dell’element tree associato:
- il framework crea uno nuovo oggetto
State
a seguito dell’invocazione del metodoStatefulWidget.createState
; - l’oggetto
State
creato viene associato alBuildContext
in modo permanente e può essere considerato montato, quindi la proprietàmounted
è impostata atrue
; - il framework invoca il metodo
initState
; - il framework invoca il metodo
didChangeDependencies
che verrà invocato ogni volta che i widget del sottoalbero vengono modificati o il widget a cui lo stato fa riferimento si sposta nella struttura; - a questo punto lo
State
è correttamente inizializzato e il suo metodobuild
può essere invocato dal framework in qualsiasi momento. L’invocazione del metodobuild
può inoltre essere forzata attraverso l’invocazione del metodosetState
; - da questo momento in poi, se il widget padre viene ricreato e richiede l’aggiornamento del sottoalbero, questo verrà distrutto e ricreato con un nuovo sottoalbero avente lo stesso
runtimeType
andWidget.key
del precedente. Quando ciò accade, il framework aggiornerà la proprietàState.widget
con il riferimento al nuovo widget creato e invocherà il metododidUpdateWidget
; - quando il sottoalbero contenente lo
State
è rimosso dall’albero, il framework invoca il metododeactivate
e può:- reinserire il sottoalbero in un altro punto dell’albero invocando il metodo
State.build
per dare alloState
la possibilità di adattarsi alla sua nuova posizione nella struttura; - invocare il metodo dispose per indicare che il sottoalbero e il relativo oggetto
State
non verranno più ricostruiti. Una volta invocato, loState
è consideratounmounted
e la proprietàmounted
viene impostata afalse
. In questo caso, non sarà più possibile rimontare loState
.
- reinserire il sottoalbero in un altro punto dell’albero invocando il metodo
Come si può evincere, gran parte dei metodi definiti per la classe State
sono direttamente utilizzati dal framework ed è incredibilmente raro doverli sovrascrivere o invocarli.
Per ulteriori informazioni si rimanda alla documentazione ufficiale di queste classi.