La tua prima app Flutter

1. Introduzione

Flutter è il toolkit dell'interfaccia utente di Google per creare applicazioni per dispositivi mobili, web e computer da un unico codebase. In questo codelab, creerai la seguente applicazione Flutter:

L'applicazione genera nomi dal suono accattivante, ad esempio "newstay", "lightstream", "mainbrake" o "graypine". L'utente può chiedere il nome successivo, aggiungere quello attuale ai preferiti e rivedere l'elenco dei nomi preferiti in una pagina separata. L'app è reattiva su schermi di dimensioni diverse.

Obiettivi didattici

  • Nozioni di base sul funzionamento di Flutter
  • Creare layout in Flutter
  • Collegare le interazioni degli utenti (ad esempio la pressione dei pulsanti) al comportamento dell'app
  • Mantenere organizzato il codice Flutter
  • Rendere l'app reattiva (per schermi diversi)
  • Aspetto coerente e la tua app

Inizierai con un'impalcatura di base in modo da poter passare direttamente alle parti interessanti.

e9c6b402cd8003fd.png

Ed ecco Filip che ti guida nell'intero codelab.

Fai clic su Avanti per iniziare il lab.

2. Configurare l'ambiente Flutter

Editor

Per rendere questo codelab il più semplice possibile, supponiamo che utilizzerai Visual Studio Code (VS Code) come ambiente di sviluppo. È senza costi e funziona su tutte le principali piattaforme.

Naturalmente puoi utilizzare qualsiasi editor tu voglia: 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 utilizzano per impostazione predefinita le scorciatoie specifiche di VS Code. È più facile pronunciare frasi come "fai clic qui" oppure "premi questo tasto" invece di qualcosa come "fai l'azione appropriata nell'editor per fare X".

228c71510a8e868.png

Scegli un target di sviluppo

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

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

Tuttavia, è pratica comune scegliere un singolo sistema operativo per il quale verrà sviluppato principalmente. Si tratta del tuo "target di sviluppo", ovvero il sistema operativo su cui viene eseguita la tua 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 collegherai un dispositivo Android al tuo laptop Windows tramite un cavo USB e l'app in fase di sviluppo viene eseguita su quel dispositivo Android collegato. Puoi anche scegliere Windows come destinazione di sviluppo, il che significa che la tua app in fase di sviluppo viene eseguita come app Windows insieme al tuo editor.

Potresti avere la tentazione di selezionare il web come target per lo sviluppo. Lo svantaggio di questa scelta è che perderai una delle funzionalità di sviluppo più utili di Flutter: Stateful Hot Reload. Flutter non può ricaricare le applicazioni web a caldo.

Fai la tua scelta ora. Ricorda: puoi sempre eseguire l'app su altri sistemi operativi in un secondo momento. È solo che avere un chiaro target di sviluppo in mente rende più semplice il passo 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 ai target di sviluppo e i plug-in dell'editor. Ricorda che, per questo codelab, devi solo installare 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 scegliere come target Windows o Xcode per scegliere macOS come target)

Nella sezione successiva creerai il tuo primo progetto Flutter.

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

Domande frequenti

3. Creare un progetto

Crea il tuo primo progetto Flutter

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

Seleziona Applicazione e una cartella in cui creare il progetto. Potrebbe essere la tua home directory o, ad esempio, C:\src\.

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

260a7d97f9678005.png

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

Ora sovrascriverai i contenuti di tre file con uno scaffold di base dell'app.

Copia e Incolla l'app iniziale

Nel riquadro sinistro di VS Code, assicurati che Explorer sia selezionato e apri il file pubspec.yaml.

e2a5bab0be07f4f7.png

Sostituisci il contenuto di questo file con il seguente:

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.1.1

dependencies:
  flutter:
    sdk: flutter

  english_words: ^4.0.0
  provider: ^6.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

Il file pubspec.yaml specifica le informazioni di base della tua app, come la versione attuale, le dipendenze e gli asset con cui verrà spedita.

A questo punto, apri un altro file di configurazione nel progetto analysis_options.yaml.

a781f218093be8e0.png

Sostituiscine i contenuti con i seguenti:

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 quanto deve essere rigoroso Flutter quando analizzi il tuo codice. Dato che questa è la tua prima incursione in Flutter, stai dicendo all'analizzatore di non esagerare. Puoi sempre modificarlo in seguito. Infatti, man mano che ti avvicini alla pubblicazione di un'effettiva app di produzione, probabilmente vorrai rendere l'analizzatore più restrittivo.

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

e54c671c9bb4d23d.png

Sostituisci il contenuto di questo file con il seguente:

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 rappresentano l'intera app fino a questo momento.

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

4. Aggiungi un pulsante

In questo passaggio viene aggiunto un pulsante Avanti per generare una nuova associazione di parole.

Avvia l'app

Per prima cosa, apri lib/main.dart e assicurati di avere selezionato il dispositivo di destinazione. Nell'angolo in basso a destra di VS Code, troverai un pulsante che mostra il dispositivo di destinazione attuale. Fai clic per modificarlo.

Mentre lib/main.dart è aperto, trova "riproduci" b0a5d0200af5985d.png nell'angolo in alto a destra della finestra di VS Code e fai clic.

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

f96e7dfb0937d7f4.png

Primo ricaricamento a caldo

In fondo a lib/main.dart, aggiungi un elemento 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 la stessa. Ecco il famoso stateful Hot Reload di Flutter al lavoro. Il ricaricamento a caldo viene attivato quando salvi le modifiche in un file di origine.

Domande frequenti

Aggiunta di un pulsante

Poi aggiungi un pulsante in fondo a Column, proprio sotto la seconda istanza 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 viene nuovamente aggiornata: viene visualizzato un pulsante e, quando fai clic su di esso, la console di debug in VS Code mostra il messaggio premuto!.

Un corso accelerato di Flutter in 5 minuti

Per quanto sia divertente guardare la console di debug, vuoi che il pulsante faccia qualcosa di più significativo. Prima di farlo, 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 è disponibile la funzione main(). Nel formato attuale, indica solo a Flutter 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(),
      ),
    );
  }
}

// ...

Il corso MyApp è valido per StatelessWidget. I widget sono gli elementi da cui crei 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 (ulteriori informazioni in seguito), assegna un nome all'app, definisce il tema visivo e imposta la "casa" widget: il punto di partenza della tua app.

lib/main.dart

// ...

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

// ...

Inoltre, la classe MyAppState definisce lo stato...bene...dell'app. Questa è la tua prima incursione in Flutter, quindi questo codelab ti semplificherà e ti concentrerà. Esistono molti modi efficaci per gestire lo stato delle app in Flutter. Uno dei più facili da spiegare è ChangeNotifier, l'approccio adottato da questa app.

  • MyAppState definisce i dati necessari per il funzionamento dell'app. Al momento, contiene solo una variabile con l'attuale coppia di parole casuali. Lo aggiungerai in seguito.
  • La classe di stato estende ChangeNotifier, il che significa che può inviare una notifica ad altri utenti delle proprie modifiche. Ad esempio, se cambia la coppia di parole corrente, è necessario che alcuni widget nell'app ne siano a conoscenza.
  • Lo stato viene creato e fornito all'intera app utilizzando un ChangeNotifierProvider (vedi il codice sopra in MyApp). In questo modo qualsiasi widget nell'app può controllare 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 sotto è mappata a un commento numero riga nel codice 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 monitora le modifiche allo stato attuale dell'app usando il metodo watch.
  3. Ogni metodo build deve restituire un widget o, più solitamente, un albero nidificato di widget. In questo caso, il widget di primo livello è Scaffold. Non lavorerai con Scaffold in questo codelab, ma è un widget utile e si trova nella maggior parte delle app Flutter reali.
  4. Column è uno dei widget di layout più basilari di Flutter. Prende un numero qualsiasi di elementi secondari e li inserisce in una colonna dall'alto verso il basso. Per impostazione predefinita, la colonna posiziona visivamente i relativi elementi secondari nella parte superiore. Presto modificherai 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 offre diversi getter utili, ad esempio asPascalCase o asSnakeCase. In questo caso utilizziamo asLowerCase, ma puoi cambiare ora se preferisci una delle alternative.
  7. Nota come il codice Flutter fa un uso intensivo delle virgole finali. Questa virgola non deve essere necessariamente presente qui, perché children è l'ultimo (e unico) membro di questo specifico elenco di parametri Column. Eppure è generalmente una buona idea utilizzare le virgole finali: rendono banale l'aggiunta di altri membri e servono anche come suggerimento per il formato automatico di Dart per inserire una nuova riga. Per ulteriori informazioni, consulta la sezione Formattazione del codice.

Successivamente, collegherai 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. Chiama anche notifyListeners()(un metodo di ChangeNotifier) che garantisce che chiunque guardi MyAppState riceva una notifica.

Non rimane 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. Ogni volta che premi il pulsante Avanti dovrebbe essere generata una nuova coppia di parole casuale.

Nella prossima sezione renderai l'interfaccia utente più bella.

5. Rendi l'app più bella

Questo è l'aspetto attuale dell'app.

3dd8a9d8653bdc56.png

Non ideale. Il pezzo forte dell'app, ovvero la coppia di parole generata in modo casuale, dovrebbe essere più visibile. Dopo tutto, è il motivo principale per cui i nostri utenti utilizzano quest'app. Inoltre, i contenuti dell'app sono stranamente decentrati e l'intera app è noioso e bianco.

Questa sezione affronta questi problemi lavorando al design dell'app. L'obiettivo finale di questa sezione è simile al seguente:

2bbee054d81a3127.png

Estrarre un widget

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

Flutter fornisce un aiuto di refactoring per estrarre widget, ma prima di utilizzarlo, assicurati che la riga estratta abbia accesso solo a ciò di cui ha bisogno. Al momento, la riga accede a appState, ma in realtà ha bisogno solo di sapere qual è l'attuale coppia di parole.

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.

A questo punto, richiama il menu Refactoring. In VS Code, puoi farlo in uno dei due modi seguenti:

  1. Fai clic con il tasto destro del mouse sulla porzione di codice di cui vuoi eseguire il refactoring (in questo caso Text) e seleziona Refactoring... dal menu a discesa.

OPPURE

  1. Sposta il cursore sulla porzione di codice di cui vuoi eseguire il refactoring (in questo caso Text) e premi Ctrl+. (Win/Linux) o Cmd+. (Mac).

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

In questo modo viene creata automaticamente una nuova classe, BigCard, alla fine del file corrente. Il corso ha un aspetto simile a questo:

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);
  }
}

// ...

Nota come l'app continua a funzionare anche durante il refactoring.

Aggiunta di una carta di credito

È il momento di trasformare questo nuovo widget nell'audace interfaccia utente che avevamo in programma all'inizio di questa sezione.

Trova la classe BigCard e il metodo build() al suo interno. Come prima, richiama il menu Refactor sul widget Text. Tuttavia, questa volta non estrarrai il widget.

Seleziona invece Aggrega con spaziatura interna. Verrà creato un nuovo widget principale attorno al widget Text chiamato Padding. Dopo il salvataggio, noterai che la parola casuale ha già più respiro.

Aumenta la spaziatura interna dal valore predefinito di 8.0. Ad esempio, utilizza 20 per una spaziatura interna più ampia.

Poi vai di livello più in alto. Posiziona il cursore sul widget Padding, apri il menu Refactoring e seleziona Aggrega con widget....

Questo ti consente di specificare il widget principale. Digita "Carta" e premi Invio.

Aggrega il widget Padding, e quindi anche Text, con un widget Card.

6031adbc0a11e16b.png

Tema e stile

Per mettere in risalto la scheda, dipingila con un colore più ricco. Inoltre, poiché è sempre opportuno mantenere una combinazione di colori coerente, utilizza 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 fanno molto lavoro:

  • Innanzitutto, il codice richiede il tema corrente dell'app con Theme.of(context).
  • Successivamente, il codice definisce il colore della scheda in modo che sia lo stesso della proprietà colorScheme del tema. La combinazione di colori contiene molti colori e primary è il colore più evidente e distintivo dell'app.

Ora la scheda è dipinta 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 di origine per ColorScheme da lì.

Osserva la fluidità dell'animazione del colore. Questa operazione è chiamata animazione implicita. Molti widget Flutter si interpolano facilmente tra i valori in modo che l'UI non si limiti tra stati.

Anche il pulsante in alto sotto la scheda cambia colore. Questa è la potenza di utilizzare un valore Theme a livello di app anziché utilizzare valori di hardcoded.

TextTheme

La scheda presenta ancora un problema: il testo è troppo piccolo e il suo 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),
      ),
    );
  }

// ...

Cosa c'è dietro questo cambiamento:

  • Se utilizzi theme.textTheme, accedi al tema dei caratteri dell'app. Questo corso include membri quali bodyMedium (per il testo standard di medie dimensioni), caption (per i sottotitoli delle immagini) o headlineLarge (per i titoli grandi).
  • La proprietà displayMedium è uno stile di grandi dimensioni pensato per il testo visualizzato. In questo caso, la parola display viene usata in senso tipografico, ad esempio in caratteri tipografici di visualizzazione. La documentazione di displayMedium afferma che "gli stili di visualizzazione sono riservati per testo breve e importante", ovvero è esattamente il nostro caso d'uso.
  • In teoria, la proprietà displayMedium del tema potrebbe essere null. Dart, il linguaggio di programmazione in cui si sta scrivendo questa app, è a sicurezza null, quindi non consente di chiamare metodi di oggetti potenzialmente null. In questo caso, tuttavia, puoi utilizzare l'operatore ! ("operatore bang") per assicurarti che Dart sappia cosa stai facendo. (displayMedium decisamente non è null in questo caso. Il motivo per cui siamo al di fuori dell'ambito di questo codelab, però.
  • La chiamata di copyWith() su displayMedium restituisce una copia dello stile di testo con le modifiche che hai definito. In questo caso, cambierai 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 all'uso sul colore principale dell'app.

Ora l'app dovrebbe avere il seguente aspetto:

2405e9342d28c193.png

Se vuoi, cambia ulteriormente la scheda. Ecco alcuni esempi:

  • copyWith() consente di modificare molto di più dello stile del testo oltre al colore. Per ottenere l'elenco completo delle proprietà che puoi modificare, posiziona il cursore in qualsiasi punto tra le parentesi di copyWith() e premi Ctrl+Shift+Space (Win/Linux) o Cmd+Shift+Space (Mac).
  • Allo stesso modo, puoi modificare altre informazioni sul 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 rispettivi onPrimary equivalenti.

Migliorare l'accessibilità

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

d1fad7944fb890ea.png

Tuttavia, a volte è necessario svolgere un po' di lavoro. Nel caso di questa app, lo screen reader potrebbe avere problemi a pronunciare alcune coppie di parole generate. Sebbene gli esseri umani non abbiano problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare ph in mezzo alla 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 distinte invece di una parola composta permette di assicurarsi che gli screen reader le identifichino in modo appropriato e di offrire 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 eseguire l'override dei contenuti visivi del widget di testo con un contenuto semantico più appropriato 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 a eseguire questa operazione utilizzando uno screen reader sul tuo dispositivo.

Centra l'UI

Ora che la coppia di parole casuale è presentata con un tocco visivo sufficiente, è il momento di posizionarla al centro della finestra/schermata 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 ignorare 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'),
          ),
        ],
      ),
    );
  }
}

// ...

Centra gli elementi secondari all'interno di Column lungo il suo asse principale (verticale).

b555d4c7f5000edf.png

Gli elementi secondari sono già centrati lungo l'asse trasversale della colonna (in altre parole, sono già centrati orizzontalmente). Tuttavia, Column in sé non è centrato all'interno di Scaffold. Possiamo verificare questa informazione utilizzando Widget Inspector.

Il Controllo widget non rientra nell'ambito di questo codelab, ma come puoi notare, quando il parametro Column è evidenziato, non occupa l'intera larghezza dell'app. Occupa solo lo spazio orizzontale necessario ai suoi figli.

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

Ora l'app dovrebbe avere il seguente aspetto:

455688d93c30d154.png

Se vuoi, puoi apportare ulteriori modifiche.

  • Puoi rimuovere il widget Text sopra BigCard. Si potrebbe sostenere che il testo descrittivo ("Un'idea casuale AWESOME:") non è più necessario poiché l'interfaccia utente ha senso anche senza. Ed è più pulita in questo modo.
  • Puoi anche aggiungere un widget SizedBox(height: 10) tra BigCard e ElevatedButton. In questo modo, la distanza tra i due widget è leggermente maggiore. Il widget SizedBox occupa spazio e non esegue automaticamente il rendering. Di solito viene utilizzato per creare "lacune" visive.

Con le modifiche facoltative, MyHomePage contiene il seguente 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 ai preferiti (o mettere Mi piace) le parole generate.

6. Aggiungi funzionalità

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

e6b01a8c90df8ffa.png

Aggiungere la logica di business

Scorri fino a MyAppState e aggiungi il codice seguente:

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 chiamata favorites. Questa proprietà è inizializzata con un elenco vuoto: [].
  • Hai inoltre specificato che l'elenco può contenere solo coppie di parole, <WordPair>[], utilizzando generics. Questo contribuisce a rendere la tua app più affidabile: Dart si rifiuta persino di eseguire l'app se provi ad aggiungere qualcosa di diverso da WordPair. A sua volta, puoi utilizzare l'elenco favorites sapendo che non ci saranno mai oggetti indesiderati (come null) nascosti al suo interno.
  • 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 successivamente chiama notifyListeners();.

Aggiungi il pulsante

Con la "logica di business" è il momento di lavorare di nuovo sull'interfaccia utente. Inserire il "Mi piace" a sinistra del pulsante "Avanti" richiede un Row. Il widget Row è l'equivalente orizzontale di Column, come hai visto in precedenza.

Innanzitutto, racchiudi il pulsante esistente in un Row. Vai al metodo build() di MyHomePage, posiziona il cursore su ElevatedButton, richiama il menu Refactoring con Ctrl+. o Cmd+. e seleziona Aggrega con riga.

Quando salvi, noterai che Row si comporta in modo simile a Column: per impostazione predefinita, i relativi elementi secondari sono raggruppati a sinistra. (Column ha raggruppato i suoi figli sopra.) Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment. Tuttavia, per scopi didattici (di apprendimento), usa mainAxisSize. Questo indica a Row di non occupare 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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

La UI è tornata come prima.

3d53d2b071e2f372.png

Dopodiché, aggiungi il pulsante Mi piace e collegalo a toggleFavorite(). Come sfida, prova innanzitutto a farlo autonomamente, senza guardare il blocco di codice riportato di seguito.

e6b01a8c90df8ffa.png

Non è un problema se non procedi esattamente come descritto di seguito. Non preoccuparti dell'icona a forma di cuore, a meno che tu non voglia una sfida impegnativa.

Va bene anche fallire, dopotutto questa è 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. Inoltre, nella parte superiore del metodo build, scegli l'icona appropriata a seconda che la coppia di parole corrente sia già nei preferiti o meno. Inoltre, nota di nuovo l'utilizzo di SizedBox, per un po' di distanza tra i due pulsanti.

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 è in grado di visualizzare i preferiti. È arrivato il momento di aggiungere una schermata completamente diversa alla nostra app. Ci vediamo alla prossima sezione.

7. Aggiungi barra di navigazione

La maggior parte delle app non può contenere tutti i contenuti in un'unica schermata. Probabilmente questa app potrebbe farlo in particolare, ma a fini didattici, creerai una schermata separata per i preferiti dell'utente. Per passare da una schermata all'altra, devi implementare la prima schermata di StatefulWidget.

f62c54f5401a187.png

Per andare all'interno di questo passaggio il prima possibile, dividi MyHomePage in 2 widget separati.

Seleziona tutti gli elementi in MyHomePage, eliminali e sostituiscili 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 della UI è pronta, ma non funziona. Fare clic su (il cuore) nella barra di navigazione.

388bc25fe198c54a.png

Esamina le modifiche.

  • Innanzitutto, l'intero contenuto di MyHomePage viene estratti 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 figli. Il primo widget è SafeArea e il secondo è un widget Expanded.
  • SafeArea garantisce che l'elemento secondario non sia oscurato da un notch hardware o una barra di stato. In questa app, il widget si trova attorno a NavigationRail per impedire, ad esempio, che i pulsanti di navigazione vengano oscurati da una barra di stato del dispositivo mobile.
  • Puoi modificare la linea extended: false in NavigationRail in true. Vengono visualizzate le etichette accanto alle icone. In un passaggio successivo, imparerai a eseguire questa operazione automaticamente quando l'app ha spazio orizzontale sufficiente.
  • La barra di navigazione ha due destinazioni (Casa e Preferiti) con le rispettive icone ed etichette. Definisce anche il valore selectedIndex corrente. Un indice selezionato pari a zero seleziona la prima destinazione, un indice selezionato di uno seleziona la seconda destinazione e così via. Per ora, è hardcoded su zero.
  • 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 di indice richiesto con print().
  • Il secondo elemento secondario di Row è il widget Expanded. I widget espansi sono estremamente utili nelle righe e nelle colonne: ti consentono di esprimere layout in cui alcuni publisher secondari occupano tutto lo spazio necessario (in questo caso SafeArea) e altri widget dovrebbero occupare il più possibile lo spazio rimanente (in questo caso Expanded). Un modo di pensare ai widget Expanded è che sono "ingordi". Se vuoi avere un'idea più chiara del ruolo di questo widget, prova a aggregare il widget SafeArea con un altro Expanded. Il layout risultante ha un aspetto simile al seguente:

6bbda6c1835a1ae.png

  • Due widget Expanded dividono tutto lo spazio orizzontale disponibile, anche se alla barra di navigazione serviva solo una piccola sezione a sinistra.
  • All'interno del widget Expanded è presente l'elemento Container colorato e all'interno del contenitore, il GeneratorPage.

Confronto tra widget stateless e stateful

Fino ad ora, MyAppState copreva tutte le esigenze del tuo stato. Ecco perché tutti i widget che hai scritto finora sono stateless. Non contengono alcuno stato modificabile. Nessuno dei widget può cambiare da solo; deve passare attraverso MyAppState.

ma le cose stanno per cambiare.

Hai bisogno di un modo per mantenere il valore del valore selectedIndex del binario di navigazione. Inoltre, vuoi poter modificare questo valore dal callback onDestinationSelected.

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

e52d9c0937cc0823.jpeg

Alcuni stati sono rilevanti solo per un singolo widget, quindi dovrebbero rimanere con quel widget.

Inserisci StatefulWidget, un tipo di widget con State. Innanzitutto, converti MyHomePage in un widget stateful.

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

L'IDE crea un nuovo corso per te, _MyHomePageState. Questa classe estende State e può quindi gestire i propri valori. (può cambiare da sé). Nota inoltre che il metodo build del vecchio widget stateless è stato spostato in _MyHomePageState (anziché rimanere nel widget). È stata eseguita parola per parola: non è stato modificato nulla all'interno del metodo build. Ora vive solo altrove.

setState

Il nuovo widget stateful deve monitorare solo una 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 in 0.
  2. Puoi usare questa nuova variabile nella definizione di NavigationRail al posto della 0 hardcoded che era presente fino a ora.
  3. Quando viene chiamato il callback onDestinationSelected, anziché semplicemente stampare il nuovo valore nella console, lo assegni a selectedIndex all'interno di una chiamata setState(). Questa chiamata è simile al metodo notifyListeners() utilizzato in precedenza e garantisce che l'interfaccia utente si aggiorni.

La barra di navigazione ora risponde all'interazione dell'utente. Tuttavia, l'area ampliata a destra rimane la stessa. Il motivo è che il codice non utilizza selectedIndex per determinare quale schermata viene visualizzata.

Utilizza selectedIndex

Inserisci il seguente codice all'inizio del metodo build di _MyHomePageState, subito 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 questa porzione di codice:

  1. Il codice dichiara una nuova variabile, page, di tipo Widget.
  2. Quindi, un'istruzione di switch assegna una schermata a page, in base al valore corrente in selectedIndex.
  3. Poiché non è ancora disponibile FavoritesPage, usa Placeholder; un pratico widget che disegna un rettangolo incrociato ovunque lo posizioni, contrassegnando quella parte dell'interfaccia utente come incompiuta.

5685cf886047f6ec.png

  1. Applicando il principio di fail-fast, l'istruzione switch assicura anche di generare un errore se selectedIndex non è né 0 né 1. In questo modo eviterai insetti in futuro. Se aggiungi una nuova destinazione alla barra di navigazione e dimentichi di aggiornare questo codice, il programma si arresta in modo anomalo in fase di sviluppo (anziché farti indovinare perché non funziona o pubblicare un codice con errori in produzione).

Ora che page contiene il widget che vuoi mostrare sulla destra, probabilmente puoi indovinare quali altre modifiche sono necessarie.

Ecco _MyHomePageState dopo questa singola 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.
            ),
          ),
        ],
      ),
    );
  }
}


// ...

L'app ora passa dalla nostra GeneratorPage al segnaposto che diventerà a breve la pagina Preferiti.

Reattività

Quindi, rendi reattiva la barra di navigazione. In altre parole, vuoi che vengano visualizzate automaticamente le etichette (utilizzando extended: true) quando c'è abbastanza spazio.

a8873894c32e0d0b.png

Flutter fornisce diversi widget che ti consentono di rendere le tue app automatiche reattive. Ad esempio, Wrap è un widget simile a Row o Column che aggrega automaticamente gli elementi secondari alla "riga" successiva (chiamata "corsa") quando non è disponibile abbastanza spazio verticale o orizzontale. È disponibile FittedBox, un widget che adatta automaticamente il proprio asset secondario allo spazio disponibile in base alle tue specifiche.

Tuttavia, NavigationRail non mostra automaticamente le etichette quando c'è spazio sufficiente perché non può sapere quale è spazio sufficiente in ogni contesto. La chiamata spetta a te, in qualità di sviluppatore.

Supponiamo che tu decida di mostrare etichette solo se MyHomePage è larga almeno 600 pixel.

Il widget da utilizzare, in questo caso, è LayoutBuilder. Consente di modificare la struttura dei widget in base alla quantità di spazio disponibile.

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

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

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

  • L'utente ridimensiona la finestra dell'app
  • L'utente ruota il telefono dalla modalità Ritratto alla modalità Orizzontale o all'indietro
  • Le dimensioni di un widget accanto a MyHomePage aumentano, riducendo le dimensioni dei vincoli di MyHomePage
  • e così via.

Ora il tuo codice può decidere se mostrare l'etichetta eseguendo una query sull'attuale constraints. Apporta la seguente modifica di una sola 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 l'app reagisce all'ambiente circostante, come le dimensioni dello schermo, l'orientamento e la piattaforma. In altre parole, è reattivo.

L'unica operazione che rimane da eseguire è sostituire il file Placeholder con una schermata Preferiti effettiva. Questo argomento verrà trattato nella prossima sezione.

8. Aggiungi una nuova pagina

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

È il momento di risolvere il problema.

Se ti senti in vena di avventure, prova a eseguire questo passaggio autonomamente. Il tuo obiettivo è mostrare l'elenco di favorites in un nuovo widget stateless, FavoritesPage, quindi mostrare il widget anziché Placeholder.

Ecco alcuni suggerimenti:

  • Se vuoi che un Column scorra, usa il widget ListView.
  • Ricorda di accedere all'istanza MyAppState da qualsiasi widget utilizzando context.watch<MyAppState>().
  • Se vuoi provare anche un nuovo widget, ListTile ha proprietà come title (in genere 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 for loop all'interno dei valori letterali raccolta. Ad esempio, se messages contiene un elenco di stringhe, puoi avere un codice simile al seguente:

f0444bba08f205aa.png

Se invece hai più familiarità con la programmazione funzionale, Dart ti consente anche di scrivere codice come messages.map((m) => Text(m)).toList(). Inoltre, ovviamente, puoi sempre creare un elenco di widget e aggiungerli in modo imperativo all'interno del metodo build.

Il vantaggio di aggiungere personalmente la pagina Preferiti è che puoi ottenere ulteriori informazioni prendendo le tue decisioni. Lo svantaggio è che potresti incorrere in problemi che non sei ancora in grado di risolvere da solo. Ricorda: non è un problema ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu riesca a migliorare lo sviluppo di Flutter nella tua prima ora e nemmeno tu dovresti.

252f7c4a212c94d2.png

Quello che segue è solo un modo per implementare la pagina dei preferiti. Il modo in cui viene implementato ti motiverà (si spera) a provare a sperimentare con il codice, migliorando l'UI e personalizzala.

Ecco il nuovo corso 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: Ancora nessun preferito*.*
  • In caso contrario, viene visualizzato un elenco (con scorrimento).
  • L'elenco inizia con un riepilogo (ad esempio, Hai 5 preferiti*.*).
  • Il codice esegue quindi l'iterazione in tutti i preferiti e crea un widget ListTile per ognuno.

A questo punto non ti resta che sostituire il widget Placeholder con un FavoritesPage. E voilà!

Puoi trovare il codice finale di questa app nel repo di codelab su GitHub.

9. Passaggi successivi

Complimenti!

Guardati! Hai creato uno scaffold non funzionale con un Column e due widget Text e l'hai trasformato in una piccola app reattiva e simpatica.

d6e3d5f736411f13.png

Argomenti trattati

  • Nozioni di base sul funzionamento di Flutter
  • Creare layout in Flutter
  • Collegare le interazioni degli utenti (ad esempio la pressione dei pulsanti) al comportamento dell'app
  • Mantenere organizzato il codice Flutter
  • Rendere reattiva la tua app
  • Aspetto coerente e la tua app

E adesso?

  • Sperimenta di più con l'app che hai scritto durante questo lab.
  • Controlla il codice di questa versione avanzata della stessa app per scoprire come aggiungere elenchi animati, gradienti, dissolvenze incrociate e altro ancora.