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

Stateful e Stateless Widget: le fondamenta di un’app Flutter

Stateful Widget e Stateless Widget: la differenza tra queste tipologie di widget offerte da Flutter, framework per sviluppare app mobile multipiattaforma.
Stateful Widget e Stateless Widget: la differenza tra queste tipologie di widget offerte da Flutter, framework per sviluppare app mobile multipiattaforma.
Link copiato negli appunti

Nelle lezioni precedenti, abbiamo introdotto alcune delle nozioni fondamentali relative al linguaggio di programmazione Dart e ai principali dettagli tecnici del framework, focalizzando l’attenzione sul processo di rendering dei widget (elemento principe in Flutter). In particolare, come già anticipato nella lezione 7, all’interno di un’app Flutter quasi tutto è un widget, inclusi l’allineamento, i testi e i layout. In particolare esistono due macro classi di widget, che sono gli Stateful e Stateless Widget.

Entrambe le classi si differenziano in un unico aspetto: l’abilità di ricaricare il widget a runtime.

Questa differenza gioca un ruolo di grande importanza nella creazione di un’applicazione interattiva e in questa lezione approfondiremo queste due classi di widget per meglio comprendere quando utilizzarle.

Tipi di Layout

Durante la creazione del layout di un’app ci troviamo a dover classificare i suoi componenti in due categorie:

Categoria Descrizione
layout statico è quel tipo di layout che non cambia una volta che è stato costruito e nessuna azione o evento generato dall’utente potrà modificare gli elementi che lo compongono
layout dinamico contrariamente al precedente, questo tipo di layout si modifica in base alle attività e agli eventi legati agli utenti. Ad esempio, in un’app di cucina l’utente che aggiunge ai preferiti una ricetta genererà un evento che modificherà l’icona dei preferiti da vuota a piena. Questa azione sottolinea che l’elemento in questione è cambiato a causa di un’azione compiuta dall’utente

Per venire incontro all’esigenza di dover modellare queste due tipologie di layout, sono state definite in Flutter due tipologie di widget, Stateful e Stateless Widget, utilizzate rispettivamente per il layout dinamico e statico.

Stateless Widget

Nella creazione di un’app, spendiamo una buona parte del nostro tempo a dover creare elementi visivi, in questo caso widget, che dipendono dalle configurazioni definite al momento della creazione da parte del nodo padre e dal BuildContext che ne gestirà la posizione nel widget tree. Infatti, un context (contesto) in Flutter è semplicemente un riferimento alla posizione del widget all’interno del widget tree e può appartenere ad un solo widget.

Una volta creati, gli stateless widget non subiranno alcuna variazione e non modificheranno il proprio comportamento nemmeno in base a eventi o azioni dell’utente.

Questa tipologia di widget è conosciuta col nome di stateless widget, e alcuni esempi tipici appartenenti a questa categoria sono Text, Row, Column, e Container.

Figura 53. Esempi di alcuni stateless widget (click per ingrandire)


Esempi di alcuni stateless widget

Per creare uno stateless widget deve essere estesa la classe StatelessWidget, che richiede la sovrascrittura del metodo build() per definire la porzione di interfaccia utente rappresentata dal widget in questione.

Il metodo build() viene tipicamente invocato:

  • la prima volta che il widget viene inserito nel widget tree in uno specifico BuildContext;
  • quando le dipendenze del widget cambiano.

L’implementazione di questo metodo deve dipendere solo da:

  • i campi del widget, che non devono cambiare nel tempo;
  • qualsiasi stato ottenuto dal contesto attraverso il metodo inheritFromWidgetOfExactType di BuildContex.

In mancanza di tali condizioni, è raccomandato utilizzare al posto di uno StatelessWidget uno StatefulWidget.

Da ciò si evince che le proprietà di uno StatelessWidget sono immutabili e l’unico modo per modificarle è la creazione di una nuova istanza di quel widget.

Creiamo, ora, il nostro primo stateless widget estendendo la classe StatelessWidget e sovrascrivendo il metodo build() come segue.

class OrangeContainer extends StatelessWidget {
  const OrangeContainer({ Key key, this.text}) : super(key: key);
  final String text;
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
        appBar: new AppBar(
          title: new Text(this.text),
        ),
        body: Center(
            child: Container(
                color: const Color(0xFFFF7F50),
                constraints: BoxConstraints(
                    maxHeight: 300.0,
                    maxWidth: 200.0,
                    minWidth: 150.0,
                    minHeight: 150.0
                ),
            )
        )
    );
  }
}

In questo esempio abbiamo creato una nuova classe chiamata OrangeContainer che definisce al suo interno:

  • il costruttore della classe per inizializzare un identificatore per il widget, ossia la Key, e il parametro text, che altro non è se non una stringa da utilizzare per l’AppBar;
  • un widget Scaffold che implementa una struttura del layout per il material design e ci permette di definire le proprietà appBar e body utilizzate rispettivamente per impostare un widget AppBar e il contenuto dello Scaffold;
  • l’AppBar che mostrerà il testo passato come parametro al momento dell’inizializzazione dello stateless widget;
  • il Container, posto al centro della schermata tramite il widget Center, che definisce al suo interno due proprietà, color e constraints.

Per richiamare questo oggetto basterà semplicemente inizializzare l’oggetto all’interno della nostra app. Immaginando di partire da un progetto appena creato, basterà cambiare all’interno della classe MyApp il valore del parametro home come segue

class MyApp extends StatelessWidget {
    . . .
            home: OrangeContainer(text: 'Lesson 10')
    . . .
    }

Il ciclo di vita di uno StatelessWidget è quindi abbastanza semplice in quanto richiede:

  • l’inizializzazione del widget;
  • l’invocazione del metodo build() per crearlo.

Eseguendo l’app, otterremo il seguente risultato.

Figura 54. Visualizzazione dello stateless widget OrangeContainer per a) Android e b) iOS (click per ingrandire)


Visualizzazione dello stateless widget OrangeContainer per a) Android e b) iOS

Stateful Widget

Compreso cosa sono gli stateless widget, diventa alquanto semplice definire gli stateful widget. Infatti, contrariamente ai primi, gli stateless widget sono dinamici: permettono di cambiare il loro contenuto nel tempo in base alle azioni o agli eventi scaturiti dall’utente e non sono relativi a un qualche stato immutabile passato all’inizializzazione dell’oggetto. I widget Stateful sono pertanto utili quando la porzione dell'interfaccia utente che si sta realizzando può cambiare in modo dinamico.

Come per gli stateless widget, gli stateful widget vengono creati estendendo la classe StatefulWidget che permette di rendere dinamico lo stato del widget. Questa tipologia di widget è, infatti, caratterizzata dal widget State che contiene i dati del widget e:

  • può essere letto in modo sincrono quando il widget viene creato;
  • potrebbe cambiare durante il ciclo di vita del widget stesso.

Lo State quindi gestisce le informazioni per interagire con il widget in termini di comportamento e layout. Pertanto, ogni volta che avverrà un cambiamento nello State ciò forzerà un ricreazione del widget al fine di apportare le modifiche, rendendole visibile all’utente finale.

Contrariamente agli StatelessWidget, negli StatefulWidget è presente una relazione tra lo State e il BuildContext. Infatti, uno State è sempre associato in modo permanente ad un BuildContext e non lo cambierà mai. Quando avviene questa associazione, lo State viene considerato come mounted.

Alcuni esempi di stateful widget sono Image, Form, e Checkbox.

Figura 55. Esempi di alcuni StatefulWidget (click per ingrandire)


Esempi di alcuni StatefulWidget

Diversamente dallo StatelessWidget, lo StatefulWidget non dispone del metodo build() per costruire il widget, ma del metodo createState() che crea uno stato mutabile per il widget in base alla posizione del widget tree. Le classi che quindi estendono StatefulWidget devono sovrascrivere questo metodo che ritornerà una nuova istanza della sottoclasse dello stato associato:

_MyState createState() => _MyState();

Il framework può richiamare questo metodo diverse volte durante tutto il ciclo di vita di uno StatefulWidget. Inoltre, se il widget viene inserito in diverse posizioni nell’albero dei widget, il framework creerà istanze separate dell’oggetto State per ogni posizione. Qualora, invece, il widget dovesse essere rimosso e reinserito nell’albero, verrà creata una nuova istanza di State.

Creiamo, ora, il nostro primo stateless widget estendendo la classe StatefulWidget e sovrascrivendo il metodo createState(). In questo esempio vogliamo creare un semplice contatore dove ogni volta che l’utente cliccherà sull’icona di + verrà incrementato il contatore.

Iniziamo col definire la classe Counter che estende lo StatefulWidget e sovrascrive il metodo createState().

class Counter extends StatefulWidget {
      Counter({Key key}) : super(key: key);
      @override
      _CounterState createState() => _CounterState();
    }

Nel metodo createState() stiamo ritornando una classe privata _CounterState() che estende a sua volta la classe State, di cui si è discusso in precedenza, ed è implementata come segue.

class _CounterState extends State<Counter> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('StatefulWidget'),
      ),
      body: Center(
          child: Text(
            '$_counter',
            style: TextStyle(fontSize: 30),
          )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

All’interno della classe è:

  • definita una variabile privata _counter che è impostata a zero;
  • implementato un metodo per incrementare il contatore, il quale a sua volta invoca internamente il metodo setState() che notifica al framework che lo stato dell’oggetto è cambiato;
  • sovrascritto il metodo build() della classe State che definisce l’interfaccia utente rappresentata da questo widget.

Diversamente dal metodo build() della classe StatelessWidget, il metodo build() di State viene invocato dal framework in diverse situazioni:

  • dopo l’invocazione del metodo initState, ossia quando lo State è inserito nell’albero;
  • dopo l’invocazione del metodo didUpdateWidget, ossia quando cambiano le configurazioni del widget;
  • dopo la ricezione di una chiamata a setState;
  • dopo che la dipendenza dello State è cambiata;
  • dopo la deattivazione e il reinserimento dello State associato al widget nel widget tree ma in una posizione differente dalla precedente.

Questi eventi rappresentano, inoltre, i momenti salienti del ciclo di vita dello State associato allo StatefulWidget.

Eseguendo l’applicazione otterremo il seguente risultato.

Figura 56a. Visualizzazione dello stateful widget Counter per Android (click per ingrandire)


Visualizzazione dello stateful widget Counter per Android

Figura 56b. Visualizzazione dello stateful widget Counter per iOS (click per ingrandire)


Visualizzazione dello stateful widget Counter per iOS

Ogni volta che l’utente cliccherà sull’icona di aggiunta, verrà incrementato il contatore che aggiornerà lo State associato al widget notificandolo al framework, il quale scatenerà nuovamente l’invocazione del metodo build() di _CounterState. Pertanto, come precedentemente sottolineato, ogni volta che l’azione dell’utente genera un cambiamento dello stato di un Widget è consigliato utilizzare uno StatefulWidget.

Stateless o stateful widget

Comprese quindi le caratteristiche di queste due categorie di widget, la domanda che sorge spontanea è:

“Ho bisogno creare un Widget Stateless o Stateful?”

Per rispondere però a questa domanda bisogna comprendere se nel ciclo di vita del widget da creare è utile considerare o meno una variabile che può cambiare nel tempo e una volta modificata costringerà il widget ad essere ricreato. Nel caso in cui, dovesse essere necessario utilizzare una variabile di questo tipo allora il widget da realizzare sarà di tipo stateful, contrariamente sarà stateless.

Vediamo alcuni esempi che possono chiarire la scelta.

Supponiamo di avere un widget che mostra all’utente finale una lista di checkbox come può essere ad esempio una lista della spesa. Ognuna di queste voci sarà quindi rappresentata attraverso un oggetto avente un titolo e uno stato che viene modificato quando l’utente cliccherà sulla checkbox. In questo caso, avremo bisogno di uno StatefulWidget al fine di tenere traccia degli stati di tutti gli oggetti nella lista e ridisegnare correttamente le checkbox.

Contrariamente, immaginiamo di avere una schermata in cui viene visualizzata una lista di ricette dove ogni elemento della lista è un widget composto da un titolo, una difficoltà e tipologia. A questo widget verrà passato solo un riferimento della ricetta che sarà usato solo per popolare il contenuto del widget, che non salverà alcuno stato poichè sta usando i dati passati dal nodo padre. In questo caso è logico usare uno StatelessWidget.

Ti consigliamo anche