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 attuale ai preferiti e rivedere l'elenco dei nomi preferiti in una pagina separata. L'app è reattiva a schermi di dimensioni diverse.

Cosa imparerai

  • 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
  • Organizzare il codice Flutter
  • Rendere la tua app adattabile (per schermi diversi)
  • Ottenere un aspetto coerente della tua app

Inizierai con una struttura di base per passare subito alle parti interessanti.

e9c6b402cd8003fd.png

Ed ecco Filip che ti guida attraverso l'intero codelab.

Fai clic su Avanti per iniziare il lab.

2. Configurare l'ambiente Flutter

Editor

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

Naturalmente puoi utilizzare l'editor che preferisci: Android Studio, altri IDE IntelliJ, Emacs, Vim o Notepad++. Funzionano tutti con Flutter.

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

228c71510a8e868.png

Scegliere un target 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 unico sistema operativo per cui sviluppare principalmente. Questo è il "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. Tuttavia, puoi anche scegliere Windows come target di sviluppo, il che significa che l'app in fase di sviluppo viene eseguita come app 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 rapido con stato. Flutter non può eseguire il ricaricamento rapido delle applicazioni web.

Fai subito la tua scelta. Ricorda: puoi sempre eseguire la tua app su altri sistemi operativi in un secondo momento. Il fatto è che avere un obiettivo di sviluppo chiaro in mente rende il passaggio successivo più semplice.

Installare 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 correlati alla destinazione 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 Windows o Xcode per macOS)

Nella sezione successiva creerai il tuo primo progetto Flutter.

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

Domande frequenti

3. Crea 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.

Successivamente, seleziona Applicazione e poi una cartella in cui creare il progetto. Potrebbe essere la tua home directory o qualcosa come C:\src\.

Infine, assegna un nome al progetto. Qualcosa come namer_app o my_awesome_namer.

260a7d97f9678005.png

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

Ora sovrascrivi i contenuti di tre file con una struttura di base dell'app.

Copiare e incollare l'app iniziale

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

e2a5bab0be07f4f7.png

Sostituisci i contenuti di questo file con i seguenti:

pubspec.yaml

name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0

environment:
  sdk: ^3.9.0

dependencies:
  flutter:
    sdk: flutter
  english_words: ^4.0.0
  provider: ^6.1.5

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0

flutter:
  uses-material-design: true

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

Poi, apri un altro file di configurazione nel progetto, analysis_options.yaml.

a781f218093be8e0.png

Sostituisci 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 il livello di rigore che Flutter deve applicare durante l'analisi del codice. Poiché è la tua prima incursione in Flutter, stai dicendo all'analizzatore di fare con calma. Puoi sempre modificarla in un secondo momento. Infatti, man mano che ti avvicini alla pubblicazione di un'app di produzione effettiva, vorrai quasi certamente rendere l'analizzatore più rigoroso.

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

e54c671c9bb4d23d.png

Sostituisci i contenuti di questo file con i seguenti:

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(
          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 coppia 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 modificarlo.

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.

Dopo circa un minuto, l'app viene avviata in modalità di debug. Per ora non sembra molto:

f96e7dfb0937d7f4.png

First Hot Reload

In fondo a 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 la stessa. Questo è il famoso Hot Reload stateful di Flutter in azione. Il ricaricamento rapido viene attivato quando salvi le modifiche a un file di origine.

Domande frequenti

Aggiungere un pulsante

Poi aggiungi un pulsante in fondo al Column, subito 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 viene aggiornata di nuovo: viene visualizzato un pulsante e, quando lo clicchi, la console di debug in VS Code mostra il messaggio button pressed! (pulsante premuto).

Un corso intensivo su Flutter in 5 minuti

Per quanto sia divertente guardare la console di debug, vuoi che il pulsante faccia 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, indica a Flutter di eseguire solo 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(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

// ...

La classe MyApp estende StatelessWidget. I widget sono gli elementi con 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 (ne parleremo più avanti), assegna un nome all'app, definisce il tema visivo e imposta il widget "Home", il punto di partenza dell'app.

lib/main.dart

// ...

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

// ...

Successivamente, la classe MyAppState definisce lo stato dell'app. Questa è la tua prima incursione in Flutter, quindi questo codelab sarà semplice e mirato. 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 necessari per il funzionamento dell'app. Al momento contiene una sola variabile con la coppia di parole casuali corrente. Aggiungerai altri dettagli in un secondo momento.
  • La classe di stato estende ChangeNotifier, il che significa che può notificare ad altri i propri cambiamenti. 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ò accedere allo 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 numero di riga nel codice riportato sopra:

  1. Ogni widget definisce un metodo build() che viene chiamato automaticamente ogni volta che cambiano le circostanze del widget, in modo che sia sempre aggiornato.
  2. MyHomePage tiene traccia delle modifiche allo stato attuale dell'app utilizzando il metodo watch.
  3. Ogni metodo build deve restituire un widget o (più comunemente) un albero di widget nidificati. In questo caso, il widget di primo livello è Scaffold. Non lavorerai con Scaffold in questo codelab, ma è un widget utile e si trova nella stragrande maggioranza delle app Flutter del mondo reale.
  4. Column è uno dei widget di layout più basilari di Flutter. Prende un numero qualsiasi di figli e li inserisce in una colonna dall'alto verso il basso. Per impostazione predefinita, la colonna posiziona visivamente i suoi elementi secondari in alto. A breve 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 fornisce diversi metodi getter utili, come asPascalCase o asSnakeCase. Qui utilizziamo asLowerCase, ma puoi modificarlo ora se preferisci una delle alternative.
  7. Nota come il codice Flutter utilizzi molto le virgole finali. Questa virgola non è necessaria perché children è l'ultimo (e anche l'unico) membro di questo elenco di parametri Column. Tuttavia, in genere è una buona idea utilizzare le virgole finali: rendono banale l'aggiunta di altri membri e fungono anche da suggerimento per il formattatore automatico di Dart per inserire un carattere di nuova riga. Per ulteriori informazioni, vedi Formattazione del codice.

A questo punto, collega 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 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 l'app ora. 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

Ecco l'aspetto attuale dell'app.

3dd8a9d8653bdc56.png

Non ideale. La parte centrale dell'app, ovvero la coppia di parole generata in modo casuale, dovrebbe essere più visibile. Dopotutto, è il motivo principale per cui i nostri utenti utilizzano questa app. Inoltre, i contenuti dell'app sono stranamente decentrati e l'intera app è in bianco e nero.

Questa sezione affronta questi problemi lavorando sulla progettazione 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 ora ha il seguente aspetto: Text(appState.current.asLowerCase). Per trasformarlo in qualcosa di più complesso, è consigliabile estrarre questa riga in un widget separato. Avere widget separati per parti logiche separate della UI è un modo importante per gestire la complessità in Flutter.

Flutter fornisce un helper di refactoring per l'estrazione dei widget, ma prima di utilizzarlo, assicurati che la riga estratta acceda solo a ciò che le serve. 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 Refactor. In VS Code, puoi farlo in due modi:

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

OPPURE

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

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

In questo modo viene creato automaticamente un nuovo corso, BigCard, alla fine del file corrente. La classe ha un aspetto simile al seguente:

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 continui a funzionare anche durante questo refactoring.

Aggiunta di una carta di credito

Ora è il momento di trasformare questo nuovo widget nel componente della UI in evidenza che avevamo immaginato all'inizio di questa sezione.

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

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

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

Poi, sali di un livello. Posiziona il cursore sul widget Padding, visualizza il menu Refactor e seleziona Wrap with widget….

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

In questo modo, il widget Padding e quindi anche Text vengono racchiusi in un widget Card.

lib/main.dart

// ...

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

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }
}

// ...

L'app ora avrà un aspetto simile a questo:

6031adbc0a11e16b.png

Tema e stile

Per far risaltare di più la scheda, colorala con un colore più intenso. Poiché è sempre una buona idea 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).
  • Il codice definisce quindi il colore della scheda in modo che sia uguale alla proprietà colorScheme del tema. La combinazione di colori contiene molti colori e primary è il colore più evidente e caratteristico dell'app.

Ora la scheda è 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 principale per ColorScheme.

Nota come il colore si anima in modo fluido. Questo tipo di animazione è chiamato animazione implicita. Molti widget Flutter interpolano in modo fluido i valori in modo che l'interfaccia utente non "salti" tra gli stati.

Anche il pulsante in rilievo sotto la scheda cambia colore. Questo è il vantaggio di utilizzare un Theme a livello di app anziché codificare i valori.

TextTheme

La carta presenta ancora un problema: il testo è troppo piccolo e il colore è difficile da leggere. Per risolvere il problema, apportate 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 di questa modifica:

  • Utilizzando theme.textTheme,, accedi al tema dei caratteri dell'app. Questa classe include membri come bodyMedium (per testo standard di medie dimensioni), caption (per le didascalie delle immagini) o headlineLarge (per i titoli grandi).
  • La proprietà displayMedium è uno stile grande pensato per il testo visualizzato. La parola display è usata qui in senso tipografico, come in carattere di visualizzazione. La documentazione per displayMedium afferma che "gli stili di visualizzazione sono riservati a testi brevi e importanti", 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, è null-safe, quindi non ti consente di chiamare metodi di oggetti potenzialmente null. In questo caso, però, puoi utilizzare l'operatore ! ("operatore bang") per assicurare a Dart che sai cosa stai facendo. displayMedium non è sicuramente null in questo caso. Il motivo per cui lo sappiamo non rientra nell'ambito di questo codelab.
  • La chiamata copyWith() su displayMedium restituisce una copia dello stile di testo con le modifiche che definisci. In questo caso, stai modificando solo il colore del testo.
  • Per ottenere il nuovo colore, devi accedere di nuovo al tema dell'app. La proprietà onPrimary della combinazione di colori definisce un colore adatto all'utilizzo sul colore principale dell'app.

L'app dovrebbe ora avere un aspetto simile al seguente:

2405e9342d28c193.png

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

  • copyWith() ti consente di modificare molti altri aspetti dello stile del testo oltre al colore. Per visualizzare l'elenco completo delle proprietà che puoi modificare, posiziona il cursore all'interno delle parentesi di copyWith() e premi Ctrl+Shift+Space (Windows/Linux) o Cmd+Shift+Space (Mac).
  • Allo stesso modo, 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 tutto il testo 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. Mentre gli esseri umani non hanno problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare ph al centro della parola come f.

Una soluzione è sostituire pair.asLowerCase con "${pair.first} ${pair.second}". Quest'ultima 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 correttamente e offre un'esperienza migliore agli utenti con disabilità visiva.

Tuttavia, potresti voler 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ù adatti agli 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 tocco visivo sufficiente, è il momento di posizionarla al centro della finestra/dello 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 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 l'asse principale (verticale).

b555d4c7f5000edf.png

I figli sono già centrati lungo l'asse cross della colonna (in altre parole, sono già centrati orizzontalmente). Ma il Column stesso non è centrato all'interno del Scaffold. Possiamo verificarlo utilizzando lo strumento di controllo dei widget.

L'ispettore widget non rientra nell'ambito di questo codelab, ma puoi notare che quando Column è evidenziato, non occupa l'intera larghezza dell'app. Occupa solo lo spazio orizzontale necessario ai suoi elementi secondari.

Puoi centrare la colonna stessa. Posiziona il cursore su Column, visualizza il menu Refactor (con Ctrl+. o Cmd+.) e seleziona Wrap with Center.

L'app dovrebbe ora avere un aspetto simile al seguente:

455688d93c30d154.png

Se vuoi, puoi perfezionare ulteriormente questo aspetto.

  • Puoi rimuovere il widget Text sopra BigCard. Si potrebbe sostenere che il testo descrittivo ("Un'idea AWESOME a caso:") non sia più necessario, in quanto la UI ha senso anche senza. In questo modo è più pulito.
  • Puoi anche aggiungere un widget SizedBox(height: 10) tra BigCard e ElevatedButton. In questo modo, i due widget sono un po' più distanti tra loro. Il widget SizedBox occupa solo spazio e non esegue il rendering di nulla. 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 ai preferiti (o "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 per sempre. Sarebbe meglio avere un modo per "ricordare" i suggerimenti migliori, ad esempio un pulsante "Mi piace".

e6b01a8c90df8ffa.png

Aggiungere 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 chiamata favorites. Questa proprietà viene inizializzata con un elenco vuoto: [].
  • Hai anche specificato che l'elenco può contenere solo coppie di parole: <WordPair>[], utilizzando generici. In questo modo la tua app è più solida: Dart si rifiuta persino di eseguirla se provi ad aggiungere qualsiasi altro valore diverso da WordPair. A sua volta, puoi utilizzare l'elenco favorites sapendo che non ci saranno mai oggetti indesiderati (come null) nascosti.
  • 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 seguito.

Aggiungere il pulsante

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

Per prima cosa, racchiudi il pulsante esistente in un Row. Vai al metodo MyHomePage di build(), posiziona il cursore su ElevatedButton, richiama il menu Refactor con Ctrl+. o Cmd+. e seleziona Wrap with Row.

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 cima.) Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment. Tuttavia, a fini didattici (di apprendimento), utilizza mainAxisSize. 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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

L'interfaccia utente è tornata alla versione precedente.

3d53d2b071e2f372.png

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

e6b01a8c90df8ffa.png

Non preoccuparti se non lo fai esattamente come descritto di seguito. Infatti, non preoccuparti dell'icona del cuore, a meno che tu non voglia una sfida davvero impegnativa.

È anche normale commettere errori, dato che è 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à nei preferiti. Inoltre, nota l'utilizzo di SizedBox per tenere i due pulsanti un po' distanti.

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. È il momento di aggiungere una schermata completamente separata alla nostra app. Ci vediamo nella prossima sezione.

7. Aggiungere la modalità di navigazione laterale

La maggior parte delle app non può contenere tutto in un'unica schermata. Questa app in particolare probabilmente 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 nocciolo di questo passaggio il prima possibile, dividi MyHomePage in due widget separati.

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

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, nota 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 bambini. Il primo widget è SafeArea, il secondo è un widget Expanded.
  • SafeArea assicura che il relativo elemento secondario non sia oscurato da una tacca hardware o da una barra di stato. In questa app, il widget si estende fino a NavigationRail per evitare che i pulsanti di navigazione vengano oscurati, ad esempio, da una barra di stato mobile.
  • Puoi modificare la riga extended: false in NavigationRail in true. In questo modo, le etichette vengono visualizzate accanto alle icone. In un passaggio futuro, imparerai a farlo automaticamente quando l'app ha spazio orizzontale sufficiente.
  • La barra di navigazione ha due destinazioni (Home e Preferiti), con le rispettive icone ed etichette. Definisce anche l'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 ora, è codificato 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 dell'indice richiesto con print().
  • Il secondo elemento figlio di Row è il widget Expanded. I widget espansi sono estremamente utili nelle righe e nelle colonne: ti consentono di esprimere layout in cui alcuni elementi secondari occupano solo lo spazio necessario (SafeArea, in questo caso) e altri widget devono occupare il maggior spazio possibile (Expanded, in questo caso). Un modo per pensare ai widget Expanded è che sono "avidi". Se vuoi comprendere meglio il ruolo di questo widget, prova a racchiudere il widget SafeArea con un altro widget Expanded. Il layout risultante sarà simile a questo:

6bbda6c1835a1ae.png

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

Widget stateless e stateful

Finora, MyAppState ha coperto tutte le tue esigenze statali. Per questo motivo, tutti i widget che hai scritto finora sono senza stato. Non contengono alcuno stato modificabile proprio. Nessuno dei widget può modificarsi autonomamente, ma deve passare attraverso 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. Ma puoi immaginare che lo stato dell'app crescerebbe rapidamente oltre ogni limite se ogni widget memorizzasse i propri valori.

e52d9c0937cc0823.jpeg

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

Inserisci il StatefulWidget, un tipo di widget che ha State. Innanzitutto, converti MyHomePage in un widget stateful.

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

L'IDE crea una nuova classe per te, _MyHomePageState. Questa classe estende State e può quindi gestire i propri valori. (Può cambiare da solo.) Tieni presente inoltre che il metodo build del vecchio widget stateless è stato spostato in _MyHomePageState (anziché rimanere nel widget). È stato spostato letteralmente: non è cambiato nulla all'interno del metodo build. Ora si trova semplicemente altrove.

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 inizializzala su 0.
  2. Utilizzi questa nuova variabile nella definizione di NavigationRail anziché il valore 0 codificato in modo permanente che era presente fino ad ora.
  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: assicura che la UI venga aggiornata.

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

Utilizza selectedIndex

Inserisci il seguente codice all'inizio 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 widget pratico che disegna un rettangolo incrociato ovunque lo posizioni, contrassegnando quella parte dell'interfaccia utente come non completata.

5685cf886047f6ec.png

  1. Applicando il principio fail-fast, l'istruzione switch si assicura anche di generare un errore se selectedIndex non è 0 o 1. In questo modo, si evitano 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 (invece di farti indovinare perché le cose non funzionano o di farti pubblicare un codice pieno di bug in produzione).

Ora che page contiene il widget che vuoi mostrare a destra, probabilmente puoi immaginare quale altro cambiamento è necessario.

Ecco _MyHomePageState dopo l'unica 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 pagina GeneratorPage al segnaposto che diventerà presto la pagina Preferiti.

Reattività

A questo punto, rendi la barra di navigazione adattabile. ovvero, far sì che le etichette vengano visualizzate automaticamente (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 esegue automaticamente il wrapping dei pannelli secondari alla "riga" successiva (chiamata "run") quando non c'è spazio verticale o orizzontale sufficiente. Esiste FittedBox, un widget che inserisce automaticamente il figlio nello spazio disponibile in base alle tue specifiche.

Tuttavia, NavigationRail non mostra automaticamente le etichette quando lo spazio è sufficiente perché non può sapere cosa è uno 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.

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

Ancora una volta, utilizza il menu Refactor 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. Richiama il menu Refactor con Ctrl+. (Windows/Linux) o Cmd+. (Mac).
  3. Seleziona Wrap with Builder (Esegui il wrapping con Builder) e premi Invio.
  4. Modifica il nome di 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 i vincoli cambiano. Ciò si verifica, ad esempio, quando:

  • L'utente ridimensiona la finestra dell'app
  • L'utente ruota lo smartphone dalla modalità verticale a quella orizzontale o viceversa
  • Alcuni widget accanto a MyHomePage aumentano di dimensioni, riducendo i vincoli di MyHomePage

Ora il tuo codice può decidere se mostrare l'etichetta eseguendo una query sul constraints corrente. Apporta la seguente modifica su 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 la tua app risponde al suo ambiente, ad esempio alle dimensioni dello schermo, all'orientamento e alla piattaforma. In altre parole, è reattivo.

L'unica cosa che rimane da fare è sostituire Placeholder con una schermata Preferiti effettiva. Questo argomento è 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 avventuroso, prova a eseguire questo passaggio da solo. Il tuo obiettivo è mostrare l'elenco di favorites in un nuovo widget stateless, FavoritesPage, e poi mostrare questo widget anziché Placeholder.

Ecco alcuni suggerimenti:

  • Se vuoi un Column scorrevole, utilizza il widget ListView.
  • Ricorda che puoi accedere all'istanza MyAppState da qualsiasi widget utilizzando context.watch<MyAppState>().
  • Se vuoi provare anche 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 loop for all'interno dei valori letterali delle raccolte. Ad esempio, se messages contiene un elenco di stringhe, puoi avere un codice come il seguente:

f0444bba08f205aa.png

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

Il vantaggio di aggiungere personalmente 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: fallire va bene ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu riesca a sviluppare con Flutter alla prima ora, e nemmeno tu dovresti.

252f7c4a212c94d2.png

Quello che segue è solo un modo per implementare la pagina dei preferiti. Il modo in cui è implementato ti (si spera) ispirerà a giocare con il codice, migliorare la UI e personalizzarla.

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: Ancora nessun preferito.
  • In caso contrario, viene visualizzato un elenco (scorribile).
  • L'elenco inizia con un riepilogo (ad esempio, Hai 5 preferiti).
  • Il codice scorre quindi tutti i preferiti e crea un widget ListTile per ciascuno.

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

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

9. Passaggi successivi

Complimenti!

Guarda un po' chi c'è! Hai preso un'impalcatura non funzionante con un Column e due widget Text e l'hai trasformata in una piccola app reattiva e deliziosa.

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
  • Organizzare il codice Flutter
  • Rendere reattiva l'app
  • Ottenere un aspetto coerente della tua app

E adesso?

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