La tua prima app Flutter

1. Introduzione

Flutter è il toolkit UI di Google per la creazione di applicazioni per mobile, web e desktop da un unico codebase. In questo codelab, creerai la seguente applicazione Flutter:

L'applicazione genera nomi accattivanti, come "newstay", "lightstream", "mainbrake" o "graypine". L'utente può chiedere il nome successivo, aggiungere quello corrente ai preferiti ed esaminare l'elenco dei preferiti in una pagina separata. L'app è adattabile a schermi di dimensioni diverse.

Obiettivi didattici

  • Nozioni di base sul funzionamento di Flutter
  • Creazione di layout in Flutter
  • Collegamento delle interazioni degli utenti (ad esempio le pressioni dei pulsanti) al comportamento dell'app
  • Organizzare il codice Flutter
  • Rendere l'app adattabile (per schermi diversi)
  • Ottenere un aspetto coerente della tua app

Inizierai con uno scheletro di base per poter passare direttamente alle parti interessanti.

e9c6b402cd8003fd.png

E ora Filip ti guiderà attraverso l'intero codelab.

Fai clic su Avanti per avviare il lab.

2. Configurare l'ambiente Flutter

Editor

Per semplificare al massimo questo codelab, presupponiamo che tu utilizzi Visual Studio Code (VS Code) come ambiente di sviluppo. È senza costi e funziona su tutte le principali piattaforme.

Ovviamente puoi utilizzare qualsiasi editor che preferisci: Android Studio, altri IDE IntelliJ, Emacs, Vim o Notepad++. Tutti funzionano con Flutter.

Ti consigliamo di utilizzare VS Code per questo codelab perché le istruzioni prevedono per impostazione predefinita le scorciatoie specifiche di VS Code. È più facile dire "fai clic qui" o "premi questo tasto" anziché "esegui l'azione appropriata nell'editor per eseguire X".

228c71510a8e868.png

Scegli una destinazione di sviluppo

Flutter è un toolkit multipiattaforma. La tua app può essere eseguita su uno dei seguenti sistemi operativi:

  • iOS
  • Android
  • Windows
  • macOS
  • Linux
  • web

Tuttavia, è prassi comune scegliere un singolo sistema operativo per cui svilupperai principalmente. Si tratta del "target di sviluppo", ovvero il sistema operativo su cui viene eseguita l'app durante lo sviluppo.

16695777c07f18e5.png

Ad esempio, supponiamo che tu stia utilizzando un laptop Windows per sviluppare un'app Flutter. Se scegli Android come target di sviluppo, in genere colleghi un dispositivo Android al laptop Windows con un cavo USB e l'app in fase di sviluppo viene eseguita sul dispositivo Android collegato. Puoi anche scegliere Windows come target di sviluppo, il che significa che l'app in fase di sviluppo viene eseguita come app per Windows insieme all'editor.

Potrebbe essere allettante selezionare il web come target di sviluppo. Lo svantaggio di questa scelta è che perdi una delle funzionalità di sviluppo più utili di Flutter: il ricaricamento caldo con stato. Flutter non può ricaricare le applicazioni web a caldo.

Scegli subito. Ricorda: puoi sempre eseguire la tua app su altri sistemi operativi in un secondo momento. È solo che avere un obiettivo di sviluppo chiaro in mente rende più agevole il passaggio successivo.

Installa Flutter

Le istruzioni più aggiornate su come installare l'SDK Flutter sono sempre disponibili all'indirizzo docs.flutter.dev.

Le istruzioni sul sito web di Flutter riguardano non solo l'installazione dell'SDK stesso, ma anche gli strumenti relativi al target di sviluppo e i plug-in dell'editor. Ricorda che, per questo codelab, devi installare solo quanto segue:

  1. SDK Flutter
  2. Visual Studio Code con il plug-in Flutter
  3. Il software richiesto dal target di sviluppo scelto (ad esempio Visual Studio per il target Windows o Xcode per il target macOS)

Nella sezione successiva, creerai il tuo primo progetto Flutter.

Se hai riscontrato problemi finora, alcune di queste domande e risposte (di StackOverflow) potrebbero esserti utili per la risoluzione dei problemi.

Domande frequenti

3. Crea un progetto

Creare il primo progetto Flutter

Avvia Visual Studio Code e apri la tavolozza dei comandi (con F1 o Ctrl+Shift+P o Shift+Cmd+P). Inizia a digitare "flutter new". Seleziona il comando Flutter: New Project (Flutter: nuovo progetto).

Quindi, seleziona Application (Applicazione) e una cartella in cui creare il progetto. Potrebbe trattarsi della tua home directory o di qualcosa di simile a C:\src\.

Infine, assegna un nome al progetto. Ad esempio namer_app o my_awesome_namer.

260a7d97f9678005.png

Ora Flutter crea la cartella del progetto e VS Code la apre.

Ora sovrascrivi i contenuti di tre file con uno scheletro di base dell'app.

Copia e incolla l'app iniziale

Nel riquadro a sinistra di VS Code, assicurati che sia selezionato Esplora e apri il file pubspec.yaml.

e2a5bab0be07f4f7.png

Sostituisci i contenuti di questo file con quanto segue:

pubspec.yaml

name: namer_app
description: A new Flutter project.

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 0.0.1+1

environment:
  sdk: ^3.6.0

dependencies:
  flutter:
    sdk: flutter

  english_words: ^4.0.0
  provider: ^6.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

Il file pubspec.yaml specifica informazioni di base sulla tua app, ad esempio la versione corrente, le dipendenze e gli asset con cui verrà rilasciata.

Apri un altro file di configurazione nel progetto, analysis_options.yaml.

a781f218093be8e0.png

Sostituisci i contenuti con quanto segue:

analysis_options.yaml

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    avoid_print: false
    prefer_const_constructors_in_immutables: false
    prefer_const_constructors: false
    prefer_const_literals_to_create_immutables: false
    prefer_final_fields: false
    unnecessary_breaks: true
    use_key_in_widget_constructors: false

Questo file determina il grado di severità di Flutter durante l'analisi del codice. Dato che è la tua prima incursione in Flutter, stai dicendo all'analizzatore di non preoccuparsi. Puoi sempre modificarlo in un secondo momento. Infatti, man mano che ti avvicini alla pubblicazione di un'app di produzione effettiva, quasi certamente vorrai rendere l'analizzatore più rigoroso.

Infine, apri il file main.dart nella directory lib/.

e54c671c9bb4d23d.png

Sostituisci i contenuti di questo file con quanto segue:

lib/main.dart

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [
          Text('A random idea:'),
          Text(appState.current.asLowerCase),
        ],
      ),
    );
  }
}

Queste 50 righe di codice costituiscono l'intera app finora.

Nella sezione successiva, esegui l'applicazione in modalità di debug e inizia a sviluppare.

4. Aggiungere un pulsante

Questo passaggio aggiunge un pulsante Avanti per generare una nuova accoppiata di parole.

Avvia l'app

Innanzitutto, apri lib/main.dart e assicurati di aver selezionato il dispositivo di destinazione. Nell'angolo in basso a destra di VS Code, troverai un pulsante che mostra il dispositivo di destinazione corrente. Fai clic per modificarla.

Mentre lib/main.dart è aperto, individua il pulsante "Riproduci" b0a5d0200af5985d.png nell'angolo in alto a destra della finestra di VS Code e fai clic su di esso.

Dopo circa un minuto, l'app viene avviata in modalità di debug. Non sembra molto, ma:

f96e7dfb0937d7f4.png

Primo ricaricamento rapido

Nella parte inferiore di lib/main.dart, aggiungi qualcosa alla stringa nel primo oggetto Text e salva il file (con Ctrl+S o Cmd+S). Ad esempio:

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),  // ← Example change.
          Text(appState.current.asLowerCase),
        ],
      ),
    );

// ...

Nota come l'app cambia immediatamente, ma la parola casuale rimane invariata. Questo è il famoso ricoricamento caldo stateful di Flutter al lavoro. Il ricaricamento rapido viene attivato quando salvi le modifiche a un file di origine.

Domande frequenti

Aggiunta di un pulsante

Aggiungi un pulsante nella parte inferiore di Column, proprio sotto la seconda istanza di Text.

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(appState.current.asLowerCase),

          // ↓ Add this.
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),

        ],
      ),
    );

// ...

Quando salvi la modifica, l'app si aggiorna di nuovo: viene visualizzato un pulsante e, quando fai clic su di esso, la console di debug in VS Code mostra il messaggio Pulsante premuto.

Un corso introduttivo a Flutter in 5 minuti

Anche se è divertente guardare la Console di debug, vorresti che il pulsante facesse qualcosa di più significativo. Prima di procedere, però, dai un'occhiata più da vicino al codice in lib/main.dart per capire come funziona.

lib/main.dart

// ...

void main() {
  runApp(MyApp());
}

// ...

Nella parte superiore del file troverai la funzione main(). Nella sua forma attuale, dice a Flutter solo di eseguire l'app definita in MyApp.

lib/main.dart

// ...

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

// ...

La classe MyApp estende StatelessWidget. I widget sono gli elementi da cui viene creata ogni app Flutter. Come puoi vedere, anche l'app stessa è un widget.

Il codice in MyApp configura l'intera app. Crea lo stato a livello di app (di seguito sono riportate ulteriori informazioni), assegna un nome all'app, definisce il tema visivo e imposta il widget "home", ovvero il punto di partenza dell'app.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

// ...

Poi, la classe MyAppState definisce lo stato dell'app. Se è la tua prima esperienza con Flutter, questo codelab manterrà la formazione semplice e mirata. Esistono molti modi efficaci per gestire lo stato dell'app in Flutter. Uno dei più semplici da spiegare è ChangeNotifier, l'approccio adottato da questa app.

  • MyAppState definisce i dati di cui l'app ha bisogno per funzionare. Al momento contiene una sola variabile con la coppia di parole casuali corrente. Aggiungerai altro in un secondo momento.
  • La classe di stato estende ChangeNotifier, il che significa che può notificare ad altri le proprie modifiche. Ad esempio, se la coppia di parole corrente cambia, alcuni widget nell'app devono saperlo.
  • Lo stato viene creato e fornito all'intera app utilizzando un ChangeNotifierProvider (vedi il codice sopra in MyApp). In questo modo, qualsiasi widget dell'app può acquisire lo stato. d9b6ecac5494a6ff.png

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {           //  1
    var appState = context.watch<MyAppState>();  //  2

    return Scaffold(                             //  3
      body: Column(                              //  4
        children: [
          Text('A random AWESOME idea:'),        //  5
          Text(appState.current.asLowerCase),    //  6
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),
        ],                                       //  7
      ),
    );
  }
}

// ...

Infine, c'è MyHomePage, il widget che hai già modificato. Ogni riga numerata di seguito corrisponde a un commento con il numero di riga nel codice riportato sopra:

  1. Ogni widget definisce un metodo build() che viene chiamato automaticamente ogni volta che le circostanze del widget cambiano, in modo che il widget sia sempre aggiornato.
  2. MyHomePage tiene traccia delle modifiche allo stato corrente dell'app utilizzando il metodo watch.
  3. Ogni metodo build deve restituire un widget o (più in genere) un albero nidificato di widget. In questo caso, il widget di primo livello è Scaffold. In questo codelab non lavorerai con Scaffold, ma si tratta di un widget utile che si trova nella maggior parte delle app Flutter reali.
  4. Column è uno dei widget di layout più semplici di Flutter. Prende un numero qualsiasi di elementi secondari e li inserisce in una colonna da cima a fondo. Per impostazione predefinita, la colonna posiziona visivamente i relativi elementi secondari in alto. A breve cambierai questa impostazione in modo che la colonna sia centrata.
  5. Hai modificato questo widget Text nel primo passaggio.
  6. Questo secondo widget Text prende appState e accede all'unico membro di quella classe, current (che è un WordPair). WordPair fornisce diversi getter utili, come asPascalCase o asSnakeCase. Qui utilizziamo asLowerCase, ma puoi modificarlo subito se preferisci una delle alternative.
  7. Nota come il codice Flutter fa un uso intensivo delle virgole finali. Questa virgola specifica non è necessaria, perché children è l'ultimo (e anche l'unico) membro di questo elenco di parametri Column. Tuttavia, in genere è buona norma utilizzare le virgole finali: semplificano l'aggiunta di altri membri e servono anche da suggerimento per l'autoformattazione di Dart per inserire un a capo. Per ulteriori informazioni, consulta la sezione Formattazione del codice.

A questo punto, dovrai collegare il pulsante allo stato.

Il tuo primo comportamento

Scorri fino a MyAppState e aggiungi un metodo getNext.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  //  Add this.
  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }
}

// ...

Il nuovo metodo getNext() riassegna current con un nuovo WordPair casuale. Inoltre, chiama notifyListeners()(un metodo di ChangeNotifier)che garantisce che chiunque guardi MyAppState riceva una notifica.

Non resta che chiamare il metodo getNext dal callback del pulsante.

lib/main.dart

// ...

    ElevatedButton(
      onPressed: () {
        appState.getNext();  // ← This instead of print().
      },
      child: Text('Next'),
    ),

// ...

Salva e prova subito l'app. Dovrebbe generare una nuova coppia di parole casuali ogni volta che premi il pulsante Avanti.

Nella sezione successiva, renderai più bella l'interfaccia utente.

5. Rendere l'app più bella

Questo è l'aspetto attuale dell'app.

3dd8a9d8653bdc56.png

Non male. Il fulcro dell'app, ovvero la coppia di parole generata in modo casuale, deve essere più visibile. Dopotutto, è il motivo principale per cui i nostri utenti utilizzano questa app. Inoltre, i contenuti dell'app sono stranamente fuori centro e l'intera app è in bianco e nero.

Questa sezione risolve questi problemi lavorando sul design dell'app. L'obiettivo finale di questa sezione è il seguente:

2bbee054d81a3127.png

Estrarre un widget

La riga responsabile della visualizzazione della coppia di parole corrente ora ha il seguente aspetto: Text(appState.current.asLowerCase). Per trasformarla in qualcosa di più complesso, è buona norma estrarre questa riga in un widget separato. Avere widget separati per parti logiche distinte dell'interfaccia utente è un modo importante per gestire la complessità in Flutter.

Flutter fornisce un'utilità di refactoring per l'estrazione dei widget, ma prima di utilizzarla assicurati che la riga estratta acceda solo a ciò che è necessario. Al momento, la riga accede a appState, ma in realtà deve solo sapere qual è la coppia di parole corrente.

Per questo motivo, riscrivi il widget MyHomePage come segue:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();  
    var pair = appState.current;                 //  Add this.

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(pair.asLowerCase),                //  Change to this.
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

Bene. Il widget Text non fa più riferimento all'intero appState.

Ora richiama il menu Ristruttura. In VS Code, puoi farlo in due modi:

  1. Fai clic con il tasto destro del mouse sul codice che vuoi sottoporre a refactoring (Text in questo caso) e seleziona Rifactoring… dal menu a discesa.

OPPURE

  1. Sposta il cursore sul codice del componente che vuoi sottoporre a refactoring (in questo caso Text) e premi Ctrl+. (Windows/Linux) o Cmd+. (Mac).

Nel menu Ristruttura, seleziona Estrai widget. Assegna un nome, ad esempio BigCard, e fai clic su Enter.

Viene creato automaticamente un nuovo livello, BigCard, alla fine del file corrente. La classe ha il seguente aspetto:

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Text(pair.asLowerCase);
  }
}

// ...

Notare come l'app continui a funzionare anche durante questo refactoring.

Aggiunta di una carta di credito

Ora è il momento di trasformare questo nuovo widget nell'elemento di UI audace che abbiamo immaginato all'inizio di questa sezione.

Trova la classe BigCard e il metodo build() al suo interno. Come prima, richiama il menu Ristruttura nel widget Text. Tuttavia, questa volta non dovrai estrarre il widget.

Seleziona invece A capo con spaziatura. Viene creato un nuovo widget principale attorno al widget Text denominato Padding. Dopo il salvataggio, vedrai che la parola casuale ha già più spazio.

Aumenta il valore predefinito di 8.0. Ad esempio, utilizza qualcosa come 20 per una spaziatura interna più ampia.

Poi, vai a un livello superiore. Posiziona il cursore sul widget Padding, apri il menu Ristruttura e seleziona Inserisci un a capo con un widget….

In questo modo puoi specificare il widget principale. Digita "Carta" e premi Invio.

Il widget Padding e quindi anche Text vengono racchiusi in un widget Card.

6031adbc0a11e16b.png

Tema e stile

Per mettere in risalto la scheda, dipingila con un colore più intenso. Poiché è sempre buona norma mantenere una combinazione di colori coerente, utilizza l'opzione Theme dell'app per scegliere il colore.

Apporta le seguenti modifiche al metodo build() di BigCard.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);       //  Add this.

    return Card(
      color: theme.colorScheme.primary,    //  And also this.
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }

// ...

Queste due nuove righe svolgono un sacco di lavoro:

  • Innanzitutto, il codice richiede il tema corrente dell'app con Theme.of(context).
  • Il codice definisce quindi il colore della scheda come uguale a quello della proprietà colorScheme del tema. La combinazione di colori contiene molti colori e primary è il colore più evidente e che definisce l'app.

La scheda viene ora colorata con il colore principale dell'app:

a136f7682c204ea1.png

Puoi modificare questo colore e la combinazione di colori dell'intera app scorrendo verso l'alto fino a MyApp e modificando il colore del seme per ColorScheme.

Nota come il colore si anima in modo fluido. Questa è chiamata animazione implicita. Molti widget Flutter eseguono l'interpolazione tra i valori in modo che l'interfaccia utente non "salti" da uno stato all'altro.

Anche il pulsante in rilievo sotto la scheda cambia colore. Ecco la potenza dell'utilizzo di un Theme a livello di app rispetto ai valori hardcoded.

TextTheme

La carta presenta ancora un problema: il testo è troppo piccolo e il colore è difficile da leggere. Per risolvere il problema, apporta le seguenti modifiche al metodo build() di BigCard.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    //  Add this.
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),
        //  Change this line.
        child: Text(pair.asLowerCase, style: style),
      ),
    );
  }

// ...

Motivo del cambiamento:

  • Se usi theme.textTheme,, accedi al tema dei caratteri dell'app. Questa classe include elementi come bodyMedium (per testo standard di medie dimensioni), caption (per le didascalie delle immagini) o headlineLarge (per titoli grandi).
  • La proprietà displayMedium è uno stile di grandi dimensioni pensato per il testo visualizzato. La parola display è usata qui nel senso tipografico, ad esempio in carattere tipografico display. La documentazione di displayMedium afferma che "gli stili di visualizzazione sono riservati a testo breve e importante", esattamente il nostro caso d'uso.
  • La proprietà displayMedium del tema potrebbe teoricamente essere null. Dart, il linguaggio di programmazione in cui stai scrivendo questa app, è sicuro per valori null, quindi non ti consente di chiamare metodi di oggetti potenzialmente null. In questo caso, però, puoi utilizzare l'operatore ! ("operatore di esclamazione") per assicurarti che Dart sappia cosa stai facendo. (displayMedium è sicuramente non nullo in questo caso. Il motivo per cui lo sappiamo esula dallo scopo di questo codelab.
  • La chiamata a copyWith() su displayMedium restituisce una copia dello stile di testo con le modifiche che definisci. In questo caso, modifichi solo il colore del testo.
  • Per ottenere il nuovo colore, accedi di nuovo al tema dell'app. La proprietà onPrimary della combinazione di colori definisce un colore adatto per l'utilizzo su un colore principale dell'app.

L'app dovrebbe avere il seguente aspetto:

2405e9342d28c193.png

Se vuoi, puoi modificare ulteriormente la scheda. Ecco alcuni esempi:

  • copyWith() ti consente di modificare molto di più dello stile del testo rispetto al semplice colore. Per visualizzare l'elenco completo delle proprietà che puoi modificare, posiziona il cursore in un punto qualsiasi tra le parentesi di copyWith() e premi Ctrl+Shift+Space (Windows/Linux) o Cmd+Shift+Space (Mac).
  • Analogamente, puoi modificare altri aspetti del widget Card. Ad esempio, puoi ingrandire l'ombra della scheda aumentando il valore del parametro elevation.
  • Prova a sperimentare con i colori. Oltre a theme.colorScheme.primary, ci sono anche .secondary, .surface e una miriade di altri. Tutti questi colori hanno i loro equivalenti onPrimary.

Migliorare l'accessibilità

Flutter rende le app accessibili per impostazione predefinita. Ad esempio, ogni app Flutter mostra correttamente tutti i testi e gli elementi interattivi dell'app agli screen reader come TalkBack e VoiceOver.

d1fad7944fb890ea.png

A volte, però, è necessario un po' di lavoro. Nel caso di questa app, lo screen reader potrebbe avere problemi a pronunciare alcune coppie di parole generate. Anche se le persone non hanno problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare la ph al centro della parola come f.

Una soluzione semplice è sostituire pair.asLowerCase con "${pair.first} ${pair.second}". Quest'ultimo utilizza l'interpolazione di stringhe per creare una stringa (ad esempio "cheap head") dalle due parole contenute in pair. L'utilizzo di due parole separate anziché di una parola composta assicura che gli screen reader le identifichino in modo appropriato e offre un'esperienza migliore agli utenti con disabilità visiva.

Tuttavia, ti consigliamo di mantenere la semplicità visiva di pair.asLowerCase. Utilizza la proprietà semanticsLabel di Text per sostituire i contenuti visivi del widget di testo con contenuti semantici più appropriati per gli screen reader:

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),

        //  Make the following change.
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }

// ...

Ora gli screen reader pronunciano correttamente ogni coppia di parole generata, ma l'interfaccia utente rimane invariata. Prova questa funzionalità utilizzando uno screen reader sul tuo dispositivo.

Centrare l'interfaccia utente

Ora che la coppia di parole casuali è presentata con un'estetica sufficiente, è il momento di posizionarla al centro della finestra/schermo dell'app.

Innanzitutto, ricorda che BigCard fa parte di un Column. Per impostazione predefinita, le colonne raggruppano i relativi elementi secondari in alto, ma possiamo facilmente sostituire questa impostazione. Vai al metodo build() di MyHomePage e apporta la seguente modifica:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,  //  Add this.
        children: [
          Text('A random AWESOME idea:'),
          BigCard(pair: pair),
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

In questo modo, gli elementi secondari vengono centrati all'interno del Column lungo l'asse principale (verticale).

b555d4c7f5000edf.png

I gruppi sono già centrati sull'asse trasversale della colonna (in altre parole, sono già centrati orizzontalmente). Tuttavia, il Column in sé non è centrato all'interno del Scaffold. Possiamo verificarlo utilizzando lo strumento di controllo dei widget.

Lo strumento di ispezione dei widget non rientra nell'ambito di questo codelab, ma puoi vedere che quando Column è evidenziato, non occupa tutta la larghezza dell'app. Occupa solo lo spazio orizzontale necessario per i suoi elementi secondari.

Puoi semplicemente centrare la colonna stessa. Posiziona il cursore su Column, richiama il menu Ristruttura (con Ctrl+. o Cmd+.) e seleziona A capo con centro.

L'app dovrebbe avere il seguente aspetto:

455688d93c30d154.png

Se vuoi, puoi modificarlo ulteriormente.

  • Puoi rimuovere il widget Text sopra BigCard. Si potrebbe sostenere che il testo descrittivo ("Un'idea FANTASTICHERIA a caso:") non è più necessario perché l'interfaccia utente ha senso anche senza. E in questo modo è più pulito.
  • Puoi anche aggiungere un widget SizedBox(height: 10) tra BigCard e ElevatedButton. In questo modo, i due widget saranno un po' più distanziati. Il widget SizedBox occupa solo spazio e non esegue il rendering di nulla da solo. Viene spesso utilizzato per creare "spazi" visivi.

Con le modifiche facoltative, MyHomePage contiene questo codice:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                appState.getNext();
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

// ...

L'app ha il seguente aspetto:

3d53d2b071e2f372.png

Nella sezione successiva aggiungerai la possibilità di aggiungere alle preferite (o mettere "Mi piace") le parole generate.

6. Aggiungere funzionalità

L'app funziona e, a volte, fornisce anche coppie di parole interessanti. Tuttavia, ogni volta che l'utente fa clic su Avanti, ogni coppia di parole scompare definitivamente. Sarebbe meglio avere un modo per "ricordare" i suggerimenti migliori, ad esempio un pulsante "Mi piace".

e6b01a8c90df8ffa.png

Aggiungi la logica di business

Scorri fino a MyAppState e aggiungi il seguente codice:

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  //  Add the code below.
  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

// ...

Esamina le modifiche:

  • Hai aggiunto una nuova proprietà a MyAppState denominata favorites. Questa proprietà viene inizializzata con un elenco vuoto: [].
  • Hai anche specificato che l'elenco può contenere solo coppie di parole: <WordPair>[], utilizzando i generici. In questo modo, la tua app sarà più solida: Dart rifiuta persino di eseguire l'app se provi ad aggiungere altro oltre a WordPair. A tua volta, puoi utilizzare l'elenco favorites sapendo che non possono esserci oggetti indesiderati (come null).
  • Hai anche aggiunto un nuovo metodo, toggleFavorite(), che rimuove la coppia di parole corrente dall'elenco dei preferiti (se è già presente) o la aggiunge (se non è ancora presente). In entrambi i casi, il codice chiama notifyListeners(); in un secondo momento.

Aggiungi il pulsante

Una volta risolta la "logica di business", è il momento di tornare a lavorare all'interfaccia utente. Per posizionare il pulsante "Mi piace" a sinistra del pulsante "Avanti" è necessario un Row. Il widget Row è l'equivalente orizzontale di Column, che hai visto in precedenza.

Innanzitutto, inserisci il pulsante esistente in un Row. Vai al metodo build() di MyHomePage, posiziona il cursore su ElevatedButton, richiama il menu Ristruttura con Ctrl+. o Cmd+. e seleziona Inserisci a capo con riga.

Quando salvi, noterai che Row si comporta in modo simile a Column: per impostazione predefinita, raggruppa i suoi elementi secondari a sinistra. (Column ha raggruppato i suoi figli in alto). Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment. Tuttavia, a fini didattici (di apprendimento), utilizza mainAxisSize. In questo modo, Row non occupa tutto lo spazio orizzontale disponibile.

Apporta la seguente modifica:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,   //  Add this.
              children: [
                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

L'interfaccia utente torna a quella precedente.

3d53d2b071e2f372.png

Aggiungi il pulsante Mi piace e collegalo a toggleFavorite(). Per una sfida, prova prima a farlo da solo, senza guardare il blocco di codice di seguito.

e6b01a8c90df8ffa.png

Non è un problema se non lo fai esattamente come descritto di seguito. In realtà, non preoccuparti dell'icona del cuore, a meno che tu non voglia davvero una sfida impegnativa.

Inoltre, è del tutto normale fallire: dopotutto, è la tua prima ora con Flutter.

252f7c4a212c94d2.png

Ecco un modo per aggiungere il secondo pulsante a MyHomePage. Questa volta, utilizza il costruttore ElevatedButton.icon() per creare un pulsante con un'icona. Nella parte superiore del metodo build, scegli l'icona appropriata a seconda che la coppia di parole corrente sia già tra i preferiti. Tieni inoltre presente l'utilizzo di SizedBox per mantenere i due pulsanti un po' distanziati.

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    //  Add this.
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [

                //  And this.
                ElevatedButton.icon(
                  onPressed: () {
                    appState.toggleFavorite();
                  },
                  icon: Icon(icon),
                  label: Text('Like'),
                ),
                SizedBox(width: 10),

                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

L'app dovrebbe avere il seguente aspetto:

Purtroppo l'utente non può vedere i preferiti. È arrivato il momento di aggiungere un'intera schermata separata alla nostra app. Ci vediamo nella prossima sezione.

7. Aggiungere la barra di navigazione laterale

La maggior parte delle app non riesce a visualizzare tutto in un'unica schermata. Probabilmente questa app in particolare potrebbe, ma a scopo didattico creerai una schermata separata per i preferiti dell'utente. Per passare da una schermata all'altra, implementerai il tuo primo StatefulWidget.

f62c54f5401a187.png

Per arrivare al punto più importante di questo passaggio il prima possibile, suddividi MyHomePage in due widget separati.

Seleziona tutto MyHomePage, eliminalo e sostituiscilo con il seguente codice:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (value) {
                print('selected: $value');
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}


class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ...

Una volta salvato, vedrai che la parte visiva dell'interfaccia utente è pronta, ma non funziona. Se fai clic su ♥︎ (il cuore) nella barra di navigazione, non succede nulla.

388bc25fe198c54a.png

Esamina le modifiche.

  • Innanzitutto, tieni presente che l'intero contenuto di MyHomePage viene estratto in un nuovo widget, GeneratorPage. L'unica parte del vecchio widget MyHomePage che non è stata estratta è Scaffold.
  • Il nuovo MyHomePage contiene un Row con due elementi secondari. Il primo widget è SafeArea e il secondo è un widget Expanded.
  • SafeArea garantisce che l'elemento secondario non sia oscurato da un notch hardware o da una barra di stato. In questa app, il widget si inserisce in NavigationRail per evitare che i pulsanti di navigazione vengano oscurati da una barra di stato mobile, ad esempio.
  • Puoi modificare la riga extended: false in NavigationRail in true. Verranno visualizzate le etichette accanto alle icone. In un passaggio successivo, scoprirai come eseguire questa operazione automaticamente quando l'app dispone di spazio orizzontale sufficiente.
  • La barra di navigazione ha due destinazioni (Home e Preferiti), con le rispettive icone ed etichette. Inoltre, definisce il valore selectedIndex corrente. Un indice selezionato pari a zero seleziona la prima destinazione, un indice selezionato pari a uno seleziona la seconda destinazione e così via. Per il momento, è impostato su zero in modo predefinito.
  • La barra di navigazione definisce anche cosa succede quando l'utente seleziona una delle destinazioni con onDestinationSelected. Al momento, l'app restituisce semplicemente il valore dell'indice richiesto con print().
  • Il secondo elemento figlio di Row è il widget Expanded. I widget espansi sono estremamente utili in righe e colonne: ti consentono di creare layout in cui alcuni elementi secondari occupano solo lo spazio necessario (in questo caso SafeArea) e altri widget devono occupare il maggior spazio possibile (in questo caso Expanded). Un modo per definire i widget Expanded è che sono "avari". Per comprendere meglio il ruolo di questo widget, prova ad avvolgere il widget SafeArea con un altro Expanded. Il layout risultante sarà simile al seguente:

6bbda6c1835a1ae.png

  • Due widget Expanded si dividono tutto lo spazio orizzontale disponibile, anche se la barra di navigazione aveva bisogno solo di un piccolo spazio a sinistra.
  • All'interno del widget Expanded è presente un Container colorato e all'interno del contenitore è presente GeneratorPage.

Widget stateless e stateful

Fino a questo momento, MyAppState ha soddisfatto tutte le tue esigenze statali. Ecco perché tutti i widget che hai scritto finora sono senza stato. Non contengono stati mutabili propri. Nessun widget può modificarsi da solo, ma deve passare per MyAppState.

Ma le cose stanno per cambiare.

Devi trovare un modo per mantenere il valore di selectedIndex della barra di navigazione. Inoltre, vuoi poter modificare questo valore dal callback onDestinationSelected.

Potresti aggiungere selectedIndex come un'altra proprietà di MyAppState. E funzionerebbe. Tuttavia, puoi immaginare che lo stato dell'app crescerebbe rapidamente oltre ogni limite se ogni widget memorizzasse i propri valori al suo interno.

e52d9c0937cc0823.jpeg

Alcuni stati sono pertinenti solo per un singolo widget, quindi devono rimanere con quel widget.

Inserisci StatefulWidget, un tipo di widget che ha State. Innanzitutto, converti MyHomePage in un widget con stato.

Posiziona il cursore sulla prima riga di MyHomePage (quella che inizia con class MyHomePage...) e richiama il menu Ristruttura utilizzando Ctrl+. o Cmd+.. Quindi seleziona Converti in StatefulWidget.

L'IDE crea un nuovo corso, _MyHomePageState. Questa classe estende State e può quindi gestire i propri valori. (Può cambiare se stessa). Inoltre, tieni presente che il metodo build del vecchio widget senza stato è passato a _MyHomePageState (anziché rimanere nel widget). È stato spostato esattamente così com'è, non è cambiato nulla all'interno del metodo build. Ora vive semplicemente da qualche altra parte.

setState

Il nuovo widget stateful deve monitorare una sola variabile: selectedIndex. Apporta le seguenti tre modifiche a _MyHomePageState:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {

  var selectedIndex = 0;     //  Add this property.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,    //  Change to this.
              onDestinationSelected: (value) {

                //  Replace print with this.
                setState(() {
                  selectedIndex = value;
                });

              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

// ...

Esamina le modifiche:

  1. Introduci una nuova variabile, selectedIndex, e la inizializzi a 0.
  2. Utilizza questa nuova variabile nella definizione di NavigationRail anziché il valore 0 hardcoded presente finora.
  3. Quando viene chiamato il callback onDestinationSelected, anziché stampare semplicemente il nuovo valore nella console, lo assegni a selectedIndex all'interno di una chiamata setState(). Questa chiamata è simile al metodo notifyListeners() utilizzato in precedenza: garantisce l'aggiornamento dell'interfaccia utente.

La barra di navigazione ora risponde all'interazione dell'utente. L'area espansa a destra rimane invariata. Questo accade perché il codice non utilizza selectedIndex per determinare quale schermata viene visualizzata.

Utilizza selectedIndex

Inserisci il seguente codice nella parte superiore del metodo build di _MyHomePageState, appena prima di return Scaffold:

lib/main.dart

// ...

Widget page;
switch (selectedIndex) {
  case 0:
    page = GeneratorPage();
    break;
  case 1:
    page = Placeholder();
    break;
  default:
    throw UnimplementedError('no widget for $selectedIndex');
}

// ...

Esamina questo codice:

  1. Il codice dichiara una nuova variabile, page, di tipo Widget.
  2. Poi, un'istruzione switch assegna una schermata a page, in base al valore corrente in selectedIndex.
  3. Poiché non esiste ancora FavoritesPage, utilizza Placeholder, un pratico widget che disegna un rettangolo barrato ovunque lo inserisci, contrassegnando quella parte dell'interfaccia utente come non completata.

5685cf886047f6ec.png

  1. Applicando il principio di fail-fast, l'istruzione switch si assicura inoltre di generare un errore se selectedIndex non è 0 o 1. In questo modo, eviterai di riscontrare bug in futuro. Se aggiungi una nuova destinazione alla barra di navigazione e dimentichi di aggiornare questo codice, il programma si arresta in modo anomalo durante lo sviluppo (anziché lasciarti indovinare il motivo per cui le cose non funzionano o pubblicare un codice con bug in produzione).

Ora che page contiene il widget che vuoi mostrare a destra, probabilmente puoi capire quale altra modifica è necessaria.

Ecco _MyHomePageState dopo l'ultima modifica rimanente:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,
              onDestinationSelected: (value) {
                setState(() {
                  selectedIndex = value;
                });
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: page,  //  Here.
            ),
          ),
        ],
      ),
    );
  }
}


// ...

Ora l'app passa dal nostro GeneratorPage al segnaposto che a breve diventerà la pagina Preferiti.

Reattività

A questo punto, rendi adattabile la barra di navigazione. In altre parole, fai in modo che mostri automaticamente le etichette (utilizzando extended: true) quando c'è spazio sufficiente.

a8873894c32e0d0b.png

Flutter fornisce diversi widget che ti aiutano a rendere le tue app automaticamente adattabili. Ad esempio, Wrap è un widget simile a Row o Column che inserisce automaticamente i componenti secondari nella "riga" successiva (chiamata "run") quando non c'è spazio verticale o orizzontale sufficiente. C'è FittedBox, un widget che adatta automaticamente i suoi elementi secondari allo spazio disponibile in base alle tue specifiche.

Tuttavia, NavigationRail non mostra automaticamente le etichette quando lo spazio è sufficiente perché non può sapere quanto spazio è sufficiente in ogni contesto. Spetta a te, in qualità di sviluppatore, prendere questa decisione.

Supponiamo che tu decida di mostrare le etichette solo se MyHomePage ha una larghezza di almeno 600 pixel.

In questo caso, il widget da utilizzare è LayoutBuilder. Ti consente di modificare l'albero dei widget in base allo spazio disponibile.

Ancora una volta, utilizza il menu Rifactoring di Flutter in VS Code per apportare le modifiche necessarie. Questa volta, però, è un po' più complicato:

  1. All'interno del metodo build di _MyHomePageState, posiziona il cursore su Scaffold.
  2. Apri il menu Ristruttura con Ctrl+. (Windows/Linux) o Cmd+. (Mac).
  3. Seleziona Inserisci un riquadro con il generatore di report e premi Invio.
  4. Modifica il nome del Builder appena aggiunto in LayoutBuilder.
  5. Modifica l'elenco dei parametri di callback da (context) a (context, constraints).

Il callback builder di LayoutBuilder viene chiamato ogni volta che cambiano le limitazioni. Ciò si verifica, ad esempio, quando:

  • L'utente ridimensiona la finestra dell'app
  • L'utente ruota lo smartphone dalla modalità Ritratto a quella Orizzontale o viceversa
  • Alcuni widget accanto a MyHomePage aumentano di dimensioni, riducendo i vincoli di MyHomePage
  • e così via.

Ora il codice può decidere se mostrare l'etichetta eseguendo una query sull'constraints corrente. Apporta la seguente modifica di una riga al metodo build di _MyHomePageState:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: Row(
          children: [
            SafeArea(
              child: NavigationRail(
                extended: constraints.maxWidth >= 600,  //  Here.
                destinations: [
                  NavigationRailDestination(
                    icon: Icon(Icons.home),
                    label: Text('Home'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite),
                    label: Text('Favorites'),
                  ),
                ],
                selectedIndex: selectedIndex,
                onDestinationSelected: (value) {
                  setState(() {
                    selectedIndex = value;
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                color: Theme.of(context).colorScheme.primaryContainer,
                child: page,
              ),
            ),
          ],
        ),
      );
    });
  }
}


// ...

Ora la tua app risponde al proprio ambiente, ad esempio dimensioni dello schermo, orientamento e piattaforma. In altre parole, è adattabile.

L'unica operazione che rimane da fare è sostituire Placeholder con una schermata Preferiti effettiva. Questo aspetto è trattato nella sezione successiva.

8. Aggiungi una nuova pagina

Ricordi il widget Placeholder che abbiamo utilizzato al posto della pagina Preferiti?

È ora di risolvere il problema.

Se ti senti in vena di avventure, prova a svolgere questo passaggio autonomamente. Il tuo obiettivo è mostrare l'elenco di favorites in un nuovo widget senza stato, FavoritesPage, e poi mostrare questo widget anziché Placeholder.

Ecco alcuni suggerimenti:

  • Se vuoi un Column che scorra, utilizza il widget ListView.
  • Ricorda che puoi accedere all'istanza MyAppState da qualsiasi widget utilizzando context.watch<MyAppState>().
  • Se vuoi anche provare un nuovo widget, ListTile ha proprietà come title (generalmente per il testo), leading (per icone o avatar) e onTap (per le interazioni). Tuttavia, puoi ottenere effetti simili con i widget che già conosci.
  • Dart consente di utilizzare i cicli for all'interno dei letterali di raccolta. Ad esempio, se messages contiene un elenco di stringhe, puoi avere un codice come il seguente:

f0444bba08f205aa.png

D'altra parte, se hai più dimestichezza con la programmazione funzionale, Dart ti consente anche di scrivere codice come messages.map((m) => Text(m)).toList(). E, ovviamente, puoi sempre creare un elenco di widget e aggiungervi elementi in modo imperativo all'interno del metodo build.

Il vantaggio di aggiungere la pagina Preferiti è che impari di più prendendo le tue decisioni. Lo svantaggio è che potresti riscontrare problemi che non sei ancora in grado di risolvere autonomamente. Ricorda: sbagliare è normale ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu padroneggi lo sviluppo Flutter nella prima ora, e nemmeno tu dovresti.

252f7c4a212c94d2.png

Di seguito è riportato solo un modo per implementare la pagina dei preferiti. Il modo in cui è implementato ti incoraggerà (ci auguriamo) a giocare con il codice, a migliorare l'interfaccia utente e a renderla tua.

Ecco la nuova classe FavoritesPage:

lib/main.dart

// ...

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

Ecco cosa fa il widget:

  • Recupera lo stato attuale dell'app.
  • Se l'elenco dei preferiti è vuoto, viene visualizzato un messaggio centrato: Nessun preferito.*
  • In caso contrario, viene visualizzato un elenco (scorrevole).
  • L'elenco inizia con un riepilogo (ad esempio Hai 5 preferiti).
  • Il codice esegue quindi un'iterazione di tutti i preferiti e crea un widget ListTile per ciascuno.

Ora non resta che sostituire il widget Placeholder con un FavoritesPage. E voilà!

Puoi ottenere il codice finale di questa app nel repository del codelab su GitHub.

9. Passaggi successivi

Complimenti!

Complimenti! Hai preso uno scafo non funzionale con un widget Column e due Text e ne hai creato una piccola app adattabile e piacevole.

d6e3d5f736411f13.png

Argomenti trattati

  • Nozioni di base sul funzionamento di Flutter
  • Creazione di layout in Flutter
  • Collegamento delle interazioni degli utenti (ad esempio le pressioni dei pulsanti) al comportamento dell'app
  • Organizzare il codice Flutter
  • Rendere l'app adattabile
  • Ottenere un aspetto coerente della tua app

E adesso?

  • Fai altri esperimenti con l'app che hai scritto durante questo lab.
  • Esamina il codice di questa versione avanzata della stessa app per scoprire come aggiungere elenchi animati, gradienti, transizioni sfumate e altro ancora.