Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 26 di 37
  • livello intermedio
Indice lezioni

Esempio pratico di creazione di uno Stateful Widget

Conoscendo le classi StatefulWidget e State ed il loro ciclo di vita, vediamo ucome creare uno stateful widget a partire da uno stateless.
Conoscendo le classi StatefulWidget e State ed il loro ciclo di vita, vediamo ucome creare uno stateful widget a partire da uno stateless.
Link copiato negli appunti

Ora che abbiamo un quadro più chiaro dei metodi offerti dalla classe StatefulWidget e dalla classe State, e dopo aver compreso il ciclo di vita di quest’ultimo, vediamo un esempio pratico di come creare uno stateful widget a partire da uno stateless.

Esempio Pratico

Creiamo un nuovo progetto come illustrato nella lezione 6 di questa guida e cancelliamo tutto eccetto la classe MyApp, definita come segue.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Lesson 24',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(title: Text('Lesson 24')),
          body: Center(child: MyWidget(traveler: "Antonio", country: "Finland")),
        )
    );
  }
}

In particolare possiamo notare che la proprietà body dello Scaffold ha come valore il widget MyWidget, definito come segue.

class MyWidget extends StatelessWidget {
  final String traveler;
  final String country;
  MyWidget({Key key, this.traveler, this.country}): super(key:key);
  @override
  Widget build(BuildContext context) {
    return Text('$traveler visited $country');
  }
}

Lo scopo di questo widget è quello di mostrare a schermo un testo contenente una stringa che rappresenta un viaggiatore e la nazione che ha visitato.

Eseguendo l’applicazione, infatti, otterremo il seguente risultato.

Figura 161. Esempio di stateless widget per a) Android b) iOS (click per ingrandire)Esempio di stateless widget per a) Android b) iOS

Immaginiamo adesso di voler trasformare questo widget in modo che l’utente possa inserire la nazione in modo dinamico modificando il testo gestito dal Text widget. Per farlo dovremo compiere i seguenti passi:

  • estendere la classe StatefulWidget, che restituisce un oggetto State tramite il metodo createState. Questa classe inoltre gestirà solo ed esclusivamente la proprietà traveler;
  • creare una classe che estenda la classe State, la quale dovrà sovrascrivere il metodo State.build e gestire la variabile country aggiornabile tramite input dall’utente;
  • invocare il metodo State.setState per modificare lo stato e scatenare una ricostruzione del widget (e del suo sottoalbero).

Partiamo dal primo passo e modifichiamo la classe MyWidget affinché estenda la classe StatefulWidget come segue.

class MyWidget extends StatefulWidget {
  // . . .
}

Successivamente, rimuoviamo dalla classe la proprietà country aggiornando di conseguenza il costruttore e implementiamo il metodo createState offerto dalla classe StatefulWidget e che obbligatoriamente dobbiamo implementare. Al termine delle modifiche otterremo il seguente risultato.

class MyWidget extends StatefulWidget {
  final String traveler;
  MyWidget({Key key, this.traveler}): super(key:key);
  @override
  State<StatefulWidget> createState() => _MyWidgetState();
}

In particolare, il metodo createState restituirà un’istanza della classe privata _MyWidgetState che definirà lo State del nostro stateless widget e la UI.

Passiamo quindi al secondo punto e creiamo la classe _MyWidgetState che dovrà:

  • gestire la variabile country rimossa dalla classe MyWidget;
  • sovrascrivere il metodo build per rappresentare la nuova interfaccia composta da due widget: un TextField per permettere all’utente di inserire un input; un Text che mostri il testo del widget.

Di seguito l’implementazione.

class _MyWidgetState extends State<MyWidget> {
  String country='';
  @override
  Widget build(BuildContext context) {
    return
      Container(
          child:Column(
            children: <Widget>[
              TextField(
                onSubmitted: (String countyName){
                  country = countyName;
                },
              ),
              Divider(),
              Text('${widget.traveler} visited $country'),
            ],
          ),
        width: 200,
        height: 200,
      );
  }
}

In questa classe abbiamo istanziato la proprietà country ad una stringa vuota, successivamente abbiamo definito un Container composto a sua volta da un Column widget, che definisce il TextField, un Divider ed il Text widget.

Più nello specifico abbiamo definito la proprietà onSubmitted del TextField affinché ogni volta che l’utente inserisce un input questo venga associato alla proprietà country, mentre per il Text widget abbiamo effettuato l’accesso al valore delle proprietà:

  • country, gestita direttamente dalla classe _MyWidgetState;
  • widget.traveler, ossia alla proprietà definita nello stateful widget MyWidget a cui possiamo accedere grazie alla proprietà State.widget che gestisce il riferimento a MyWidget.

Aggiorniamo quindi la proprietà body dello Scaffold definito nella classe MyApp come segue.

body: Center(child: MyWidget(traveler: "Antonio")),

Abbiamo quindi passato allo stateful widget MyWidget solo la proprietà traveler poichè la country viene gestita tramite input. Eseguiamo l’applicazione per ottenere il risultato in figura.

Figura 162. Inserimento di un input testuale che non scatena la ricostruzione del widget Text per a) Android b) iOS (click per ingrandire)Inserimento di un input testuale che non scatena la ricostruzione del widget Text per a) Android b) iOS

Come possiamo notare, nonostante sia stato inserito il testo Finland nel TextField, l’informazione non viene mostrata nel Text widget in quanto non è cambiato lo stato dell’oggetto State associato allo stateful element.

Per risolvere questo inconveniente possiamo passare al terzo punto del nostro elenco, ossia invocare il metodo State.setState per modificare lo stato e scatenare una ricostruzione del widget.

Per farlo, dobbiamo semplicemente modificare il valore della proprietà TextField.onSubmitted come segue:

onSubmitted: (String countyName){
  setState(() {
    country = countyName;
  });
},

Così facendo stiamo aggiornando lo stato scatenando la ricreazione del widget come mostrato nella seguente figura.

Figura 163a. Esempio funzionante di uno stateful widget per Android (click per ingrandire)Esempio funzionante di uno stateful widget per Android
Figura 163b. Esempio funzionante di uno stateful widget per iOS (click per ingrandire)Esempio funzionante di uno stateful widget per iOS

In questo modo, ogni volta che inseriremo un nuovo testo verrà aggiornato finalmente anche il Text widget con il nuovo valore inserito.

Dietro le quinte

Schematicamente, quello che accade è rappresentato dalla seguente figura.

Figura 164. Schema del widget ed element tree in presenza di uno stateful widget (click per ingrandire)Schema del widget ed element tree in presenza di uno stateful widget

Alla creazione del widget nel widget tree, viene richiesta dal framework la creazione di uno stateful element relativo al widget. A questo punto lo stateful element richiede al widget la creazione di un oggetto State ed è proprio in questo caso che viene invocato il metodo StatefulWidget.createState, che abbiamo sovrascritto nella classe MyWidget. Questo metodo quindi crea un nuovo oggetto MyWidgetState che sarà preso dallo stateful element. A questo punto, il MyWidgetState invocherà il metodo build per costruire l’interfaccia e per farlo avrà bisogno proprio delle due proprietà di interesse, ossia traveler fornita da MyWidget e country gestita dall’oggetto _MyWidgetState. A questo punto viene inserito all’interno del sottoalbero di MyWidget lo stateless widget Text contenente la stringa corretta, e nell’element tree verrà montato un nuovo stateless element.

Nel momento in cui l’utente inserirà una nuova nazione visitata, verrà invocato il metodo setState che aggiorna il valore country gestito da MyWidgetState. A questo punto l’oggetto State marcherà il suo stateful element come dirty, richiedendo quindi la ricreazione dei suoi widget figli nel frame successivo. Con il frame successivo, lo stateful element invocherà il metodo MyWidgetState.build per ricostruire il widget e mostrare il nuovo testo attraverso il Text widget. Il vecchio Text widget verrà quindi distrutto e sostituito dal nuovo e, proprio perché il nuovo widget ha lo stesso runtimeType e Widget.key del precedente, lo stateless element non verrà rimosso e aggiornerà la sua referenza al nuovo widget come mostrato in figura.

Figura 165. Schema di aggiornamento della variabile MyWidgetState.country (click per ingrandire)Schema di aggiornamento della variabile MyWidgetState.country

Considerazioni

Abbiamo visto un esempio semplice ma efficace per comprendere appieno quello che succede nel ciclo di vita di uno stateful widget. Ciononostante, imparando a conoscere sempre di più questo framework, le occasioni in cui andremo ad utilizzare uno stateful widget saranno sempre di meno, in quanto molti dei principali casi d’uso in cui utilizzarli sono già implementati dal framework stesso. Un esempio è proprio offerto dalla classe StreamBuilder, che ricostruisce il widget ogni volta che l’oggetto Stream fornisce un nuovo valore.

Il codice di questa lezione è disponibile su Github.

Ti consigliamo anche