Immergiti nei pattern e nei record di Dart

Esplora i pattern e i record di Dart

Informazioni su questo codelab

subjectUltimo aggiornamento: apr 4, 2025
account_circleScritto da: John Ryan and Marya Belanger

1. Introduzione

Dart 3 introduce nel linguaggio i pattern, una nuova importante categoria di grammatica. Oltre a questo nuovo modo di scrivere codice Dart, sono disponibili diversi altri miglioramenti del linguaggio, tra cui

  • record per raggruppare dati di tipi diversi,
  • modificatori di classe per controllare l'accesso e
  • nuove espressioni switch e istruzioni if-case.

Queste funzionalità ampliano le opzioni a tua disposizione quando scrivi codice Dart. In questo codelab imparerai a utilizzarli per rendere il codice più compatto, snello e flessibile.

Questo codelab presuppone che tu abbia una certa familiarità con Flutter e Dart. Se hai bisogno di ripassare le nozioni di base, consulta le seguenti risorse:

Cosa creerai

Questo codelab crea un'applicazione che mostra un documento JSON in Flutter. L'applicazione simula il JSON proveniente da una fonte esterna. Il formato JSON contiene i dati del documento, come la data di modifica, il titolo, le intestazioni e i paragrafi. Scrivi codice per imballare ordinatamente i dati nei record in modo che possano essere trasferiti e simballati ovunque ne abbiano bisogno i widget Flutter.

Poi utilizzi i pattern per creare il widget appropriato quando il valore corrisponde a quel pattern. Scoprirai anche come utilizzare i pattern per destrutturare i dati in variabili locali.

L'applicazione finale che crei in questo codelab, un documento con un titolo, la data dell'ultima modifica, intestazioni e paragrafi.

Cosa imparerai a fare

  • Come creare un record che memorizzi più valori con tipi diversi.
  • Come restituire più valori da una funzione utilizzando un record.
  • Come utilizzare i pattern per associare, convalidare e destrutturare i dati da record e altri oggetti.
  • Come associare i valori corrispondenti ai pattern a variabili nuove o esistenti.
  • Come utilizzare le nuove funzionalità dell'istruzione switch, le espressioni switch e le istruzioni if-case.
  • Come sfruttare il controllo di esattezza per assicurarti che ogni caso venga gestito in un'istruzione o un'espressione di switch.

2. Configura l'ambiente

  1. Installa l'SDK Flutter.
  2. Configura un editor come Visual Studio Code (VS Code).
  3. Segui i passaggi per la configurazione della piattaforma per almeno una piattaforma di destinazione (iOS, Android, computer o un browser web).

3. Crea il progetto

Prima di approfondire pattern, record e altre nuove funzionalità, prenditi un momento per creare un semplice progetto Flutter per cui scrivere tutto il codice.

Creare un progetto Flutter

  1. Utilizza il comando flutter create per creare un nuovo progetto denominato patterns_codelab. Il flag --empty impedisce la creazione dell'app contatore standard nel file lib/main.dart, che dovrai comunque rimuovere.
flutter create --empty patterns_codelab
  1. Quindi, apri la directory patterns_codelab utilizzando VS Code.
code patterns_codelab

Uno screenshot di VS Code che mostra il progetto creato con il comando "flutter create".

Impostare la versione SDK minima

  • Imposta il vincolo della versione dell'SDK per il progetto in modo che dipenda da Dart 3 o versioni successive.

pubspec.yaml

environment:
  sdk: ^3.0.0

4. Configura il progetto

In questo passaggio, crei o aggiorni due file Dart:

  • Il file main.dart contenente i widget per l'app e
  • Il file data.dart che fornisce i dati dell'app.

Continuerai a modificare entrambi i file nei passaggi successivi.

Definisci i dati per l'app

  • Crea un nuovo file, lib/data.dart, e aggiungi il seguente codice:

lib/data.dart

import 'dart:convert';

class Document {
 
final Map<String, Object?> _json;
 
Document() : _json = jsonDecode(documentJson);
}

const documentJson = '''
{
  "metadata": {
    "title": "My Document",
    "modified": "2023-05-10"
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    {
      "type": "p",
      "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
    },
    {
      "type": "checkbox",
      "checked": false,
      "text": "Learn Dart 3"
    }
  ]
}
''';

Immagina un programma che riceve dati da un'origine esterna, ad esempio uno stream di I/O o una richiesta HTTP. In questo codelab, semplifichi questo caso d'uso più realistico simulando i dati JSON in entrata con una stringa di più righe nella variabile documentJson.

I dati JSON sono definiti nella classe Document. Più avanti in questo codelab, aggiungerai funzioni che restituiscono i dati dal JSON analizzato. Questa classe definisce e inizializza il campo _json nel suo costruttore.

Esegui l'app

Il comando flutter create crea il file lib/main.dart all'interno della struttura dei file Flutter predefinita.

  1. Per creare un punto di partenza per l'applicazione, sostituisci i contenuti di main.dart con il seguente codice:

lib/main.dart

import 'package:flutter/material.dart';

import 'data.dart';

void main() {
 
runApp(const DocumentApp());
}

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

 
@override
 
Widget build(BuildContext context) {
   
return MaterialApp(
     
theme: ThemeData(useMaterial3: true),
     
home: DocumentScreen(
       
document: Document(),
     
),
   
);
 
}
}

class DocumentScreen extends StatelessWidget {
 
final Document document;

 
const DocumentScreen({
   
required this.document,
   
super.key,
 
});

 
@override
 
Widget build(BuildContext context) {
   
return Scaffold(
     
appBar: AppBar(
       
title: const Text('Title goes here'),
     
),
     
body: const Column(
       
children: [
         
Center(
           
child: Text('Body goes here'),
         
),
       
],
     
),
   
);
 
}
}

Hai aggiunto all'app i seguenti due widget:

  • DocumentApp configura la versione più recente di Material Design per il tema dell'interfaccia utente.
  • DocumentScreen fornisce il layout visivo della pagina utilizzando il widget Scaffold.
  1. Per assicurarti che tutto funzioni correttamente, esegui l'app sulla macchina host facendo clic su Esegui ed esegui il debug:

Un&#39;immagine del pulsante &quot;Esegui e debugga&quot;, disponibile nella sezione &quot;Esegui e debugga&quot; della barra delle attività a sinistra.

  1. Per impostazione predefinita, Flutter sceglie la piattaforma di destinazione disponibile. Per modificare la piattaforma di destinazione, seleziona la piattaforma corrente nella barra di stato:

Uno screenshot del selettore della piattaforma di destinazione in VS Code.

Dovresti vedere un frame vuoto con gli elementi title e body definiti nel widget DocumentScreen:

Uno screenshot dell&#39;applicazione creata in questo passaggio.

5. Crea e restituisce record

In questo passaggio, utilizzi i record per restituire più valori da una chiamata di funzione. Poi, chiami la funzione nel widget DocumentScreen per accedere ai valori e rifletterli nell'interfaccia utente.

Creare e restituire un record

  • In data.dart, aggiungi un nuovo metodo getter alla classe Document denominato metadata che restituisce un record:

lib/data.dart

import 'dart:convert';

class Document {
 
final Map<String, Object?> _json;
 
Document() : _json = jsonDecode(documentJson);

 
(String, {DateTime modified}) get metadata {           // Add from here...
   
const title = 'My Document';
   
final now = DateTime.now();

   
return (title, modified: now);
 
}                                                      // to here.
}

Il tipo restituito da questa funzione è un record con due campi, uno di tipo String e l'altro di tipo DateTime.

L'istruzione return crea un nuovo record racchiudendo i due valori tra parentesi, (title, modified: now).

Il primo campo è posizionale e senza nome, mentre il secondo è denominato modified.

Accedere ai campi dei record

  1. Nel widget DocumentScreen, chiama il metodo getter metadata nel metodo build per recuperare il record e accedere ai relativi valori:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final metadataRecord = document.metadata;              // Add this line.

    return Scaffold(
      appBar: AppBar(
        title: Text(metadataRecord.$1),                    // Modify this line,
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified ${metadataRecord.modified}',  // And this one.
            ),
          ),
        ],
      ),
    );
  }
}

Il metodo getter metadata restituisce un record, che viene assegnato alla variabile locale metadataRecord. I record sono un modo semplice e leggero per restituire più valori da una singola chiamata di funzione e assegnarli a una variabile.

Per accedere ai singoli campi composti in quel record, puoi utilizzare la sintassi del getter integrata dei record.

  • Per ottenere un campo posizionale (un campo senza nome, ad esempio title), utilizza il getter $<num> nel record. Verranno restituiti solo i campi senza nome.
  • I campi denominati come modified non hanno un getter posizionale, quindi puoi utilizzare direttamente il nome, ad esempio metadataRecord.modified.

Per determinare il nome di un getter per un campo posizionale, inizia da $1 e salta i campi denominati. Ad esempio:

var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1);                               // prints y
print(record.$2);                               // prints z
  1. Esegui il ricaricamento dinamico per visualizzare i valori JSON visualizzati nell'app. Il plug-in Dart di VS Code esegue il ricaricamento dinamico ogni volta che salvi un file.

Uno screenshot dell&#39;app, che mostra il titolo e la data di modifica.

Puoi vedere che ogni campo ha mantenuto il proprio tipo.

  • Il metodo Text() accetta una stringa come primo argomento.
  • Il campo modified è un DateTime e viene convertito in String utilizzando l'interpolazione di stringhe.

L'altro modo sicuro per restituire tipi diversi di dati è definire una classe, che è più dettagliata.

6. Corrispondenza e destrutturazione con pattern

I record possono raccogliere in modo efficiente diversi tipi di dati e trasmetterli facilmente. Ora migliora il codice utilizzando i pattern.

Un pattern rappresenta una struttura che può assumere uno o più valori, ad esempio un blueprint. I pattern vengono confrontati con i valori effettivi per determinare se corrispondono.

Alcuni pattern, quando corrispondono, destrutturano il valore corrispondente estraendo i dati. La destrutturazione ti consente di estrarre i valori da un oggetto per assegnarli a variabili locali o eseguire ulteriori corrispondenze su di essi.

Destrutturare un record in variabili locali

  1. Ristruttura il metodo build di DocumentScreen in modo da chiamare metadata e utilizzarlo per inizializzare una dichiarazione di variabile di pattern:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, modified: modified) = document.metadata;   // Modify

    return Scaffold(
      appBar: AppBar(
        title: Text(title),                                  // Modify
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified $modified',                     // Modify
            ),
          ),
        ],
      ),
    );
  }
}

Il pattern di record (title, modified: modified) contiene due pattern di variabili che corrispondono ai campi del record restituito da metadata.

  • L'espressione corrisponde al pattern secondario perché il risultato è un record con due campi, uno dei quali è denominato modified.
  • Poiché corrispondono, il pattern di dichiarazione delle variabili destruttura l'espressione, accedendo ai relativi valori e associandoli a nuove variabili locali degli stessi tipi e nomi, String title e DateTime modified.

Esiste una scorciatoia per quando il nome di un campo e la variabile che lo compila sono uguali. Esegui il refactoring del metodo build di DocumentScreen come segue.

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;            // Modify

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified $modified',
            ),
          ),
        ],
      ),
    );
  }
}

La sintassi del pattern di variabile :modified è un'abbreviazione di modified: modified. Se vuoi una nuova variabile locale con un nome diverso, puoi scrivere modified: localModified.

  1. Esegui il ricaricamento rapido per visualizzare lo stesso risultato del passaggio precedente. Il comportamento è esattamente lo stesso; hai solo reso il codice più conciso.

7. Utilizzare i pattern per estrarre i dati

In determinati contesti, i pattern non solo corrispondono e destrutturano, ma possono anche prendere una decisione su cosa fa il codice, in base alla corrispondenza o meno del pattern. Questi sono chiamati pattern confutabili.

Il pattern di dichiarazione della variabile utilizzato nell'ultimo passaggio è un pattern inconfutabile: il valore deve corrispondere al pattern, altrimenti si verifica un errore e la destrutturazione non viene eseguita. Pensa a una dichiarazione o assegnazione di variabili: non puoi assegnare un valore a una variabile se non sono dello stesso tipo.

I pattern confutabili, invece, vengono utilizzati nei contesti di flusso di controllo:

  • Si aspettano che alcuni valori rispetto ai quali viene eseguito il confronto non corrispondano.
  • Hanno lo scopo di influire sul flusso di controllo, a seconda che il valore corrisponda o meno.
  • Se non corrispondono, non interrompono l'esecuzione con un errore, ma passano semplicemente all'istruzione successiva.
  • Possono destrutturare e associare variabili utilizzabili solo quando corrispondono

Leggere i valori JSON senza pattern

In questa sezione leggi i dati senza corrispondenza di pattern per capire in che modo i pattern possono aiutarti a lavorare con i dati JSON.

  • Sostituisci la versione precedente di metadata con una che legga i valori dalla mappa _json. Copia e incolla questa versione di metadata nel corso Document:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json.containsKey('metadata')) {                     // Modify from here...
      final metadataJson = _json['metadata'];
      if (metadataJson is Map) {
        final title = metadataJson['title'] as String;
        final localModified =
            DateTime.parse(metadataJson['modified'] as String);
        return (title, modified: localModified);
      }
    }
    throw const FormatException('Unexpected JSON');          // to here.
  }
}

Questo codice convalida che i dati siano strutturati correttamente senza utilizzare pattern. In un passaggio successivo, utilizzerai la corrispondenza pattern per eseguire la stessa convalida utilizzando meno codice. Esegue tre controlli prima di procedere:

  • Il formato JSON contiene la struttura di dati prevista: if (_json.containsKey('metadata'))
  • I dati hanno il tipo previsto: if (metadataJson is Map)
  • I dati non sono null, il che viene confermato implicitamente nel controllo precedente.

Leggere i valori JSON utilizzando un pattern di mappa

Con un pattern confutabile, puoi verificare che il JSON abbia la struttura prevista utilizzando un pattern di mappa.

  • Sostituisci la versione precedente di metadata con questo codice:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json                                                // Modify from here...
        case {
          'metadata': {
            'title': String title,
            'modified': String localModified,
          }
        }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }                                                        // to here.
  }
}

Qui viene mostrato un nuovo tipo di istruzione if (introdotta in Dart 3), la istruzione if-case. Il corpo della richiesta viene eseguito solo se il pattern della richiesta corrisponde ai dati in _json. Questa corrispondenza esegue gli stessi controlli che hai scritto nella prima versione di metadata per convalidare il JSON in entrata. Questo codice convalida quanto segue:

  • _json è un tipo di mappa.
  • _json contiene una chiave metadata.
  • _json non è null.
  • _json['metadata'] è anche un tipo di mappa.
  • _json['metadata'] contiene le chiavi title e modified.
  • title e localModified sono stringhe e non null.

Se il valore non corrisponde, il pattern si contraddice (rifiuta di continuare l'esecuzione) e passa alla clausola else. Se la corrispondenza è riuscita, il pattern destruttura i valori di title e modified dalla mappa e li associa a nuove variabili locali.

Per un elenco completo dei pattern, consulta la tabella nella sezione Pattern della specifica della funzionalità.

8. Preparare l&#39;app per più pattern

Finora hai trattato la parte metadata dei dati JSON. In questo passaggio perfezioni un po' di più la logica di business per gestire i dati nell'elenco blocks e visualizzarli nella tua app.

{
  "metadata": {
    // ...
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    // ...
  ]
}

Crea una classe che memorizza i dati

  • Aggiungi una nuova classe, Block, a data.dart, che viene utilizzata per leggere e memorizzare i dati di uno dei blocchi nei dati JSON.

lib/data.dart

class Block {
  final String type;
  final String text;
  Block(this.type, this.text);

  factory Block.fromJson(Map<String, dynamic> json) {
    if (json case {'type': final type, 'text': final text}) {
      return Block(type, text);
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }
}

Il costruttore di fabbrica fromJson() utilizza la stessa condizione if con un pattern di mappa che hai già visto.

Vedrai che i dati JSON assomigliano allo schema previsto, anche se contengono un'informazione aggiuntiva chiamata checked che non è presente nello schema. Questo perché, quando utilizzi questi tipi di pattern (chiamati "pattern di mappa"), vengono prese in considerazione solo le cose specifiche che hai definito nel pattern e viene ignorato tutto il resto nei dati.

Restituisce un elenco di oggetti Block

  • Aggiungi una nuova funzione, getBlocks(), alla classe Document. getBlocks() analizza il JSON in istanze della classe Block e restituisce un elenco di blocchi da visualizzare nell'interfaccia utente:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json
        case {
          'metadata': {
            'title': String title,
            'modified': String localModified,
          }
        }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }
  }

  List<Block> getBlocks() {                                  // Add from here...
    if (_json case {'blocks': List blocksJson}) {
      return [for (final blockJson in blocksJson) Block.fromJson(blockJson)];
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }                                                          // to here.
}

La funzione getBlocks() restituisce un elenco di oggetti Block, che utilizzerai in un secondo momento per creare l'interfaccia utente. Un'istruzione if-case familiare esegue la convalida e esegue il casting del valore dei metadati blocks in un nuovo List denominato blocksJson (senza pattern, è necessario il metodo toList() per eseguire il casting).

Il valore letterale dell'elenco contiene una raccolta per per riempire il nuovo elenco con oggetti Block.

Questa sezione non introduce funzionalità correlate ai pattern che non hai già provato in questo codelab. Nel passaggio successivo, ti prepari a visualizzare gli elementi dell'elenco nell'interfaccia utente.

9. Utilizzare i pattern per visualizzare il documento

Ora puoi destrutturare e ricomporre i dati JSON utilizzando un'istruzione if-case e pattern confutabili. Tuttavia, la condizione if è solo uno dei miglioramenti alle strutture di flusso di controllo forniti dai pattern. Ora applica le tue conoscenze degli schemi confutabili alle istruzioni switch.

Controllare cosa viene visualizzato utilizzando pattern con istruzioni switch

  • In main.dart, crea un nuovo widget, BlockWidget, che determina lo stile di ogni blocco in base al relativo campo type.

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;
    switch (block.type) {
      case 'h1':
        textStyle = Theme.of(context).textTheme.displayMedium;
      case 'p' || 'checkbox':
        textStyle = Theme.of(context).textTheme.bodyMedium;
      case _:
        textStyle = Theme.of(context).textTheme.bodySmall;
    }

    return Container(
      margin: const EdgeInsets.all(8),
      child: Text(
        block.text,
        style: textStyle,
      ),
    );
  }
}

L'istruzione switch nel metodo build attiva il campo type dell'oggetto block.

  1. La prima istruzione case utilizza un pattern di stringa costante. Il pattern corrisponde se block.type è uguale al valore costante h1.
  2. La seconda istruzione case utilizza un pattern OR logico con due pattern di stringhe costanti come sottopattern. Il pattern corrisponde se block.type corrisponde a uno dei pattern secondari p o checkbox.
  1. Il caso finale è uno schema con caratteri jolly, _. I caratteri jolly nelle istruzioni switch corrispondono a tutti gli altri. Hanno lo stesso comportamento delle clausole default, che sono ancora consentite nelle istruzioni switch (sono solo un po' più verbose).

I pattern con caratteri jolly possono essere utilizzati ovunque sia consentito un pattern, ad esempio in un pattern di dichiarazione di variabili: var (title, _) = document.metadata;

In questo contesto, il carattere jolly non lega alcuna variabile. Il secondo campo viene ignorato.

Nella sezione successiva scoprirai altre funzionalità degli switch dopo aver visualizzato gli oggetti Block.

Mostra i contenuti del documento

Crea una variabile locale che contenga l'elenco di oggetti Block chiamando getBlocks() nel metodo build del widget DocumentScreen.

  1. Sostituisci il metodo build esistente in DocumentationScreen con questa versione:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final blocks = document.getBlocks();                           // Add this line

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Text('Last modified: $modified'),                        // Modify from here
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),                                                       // to here.
        ],
      ),
    );
  }
}

La riga BlockWidget(block: blocks[index]) crea un widget BlockWidget per ogni elemento dell'elenco di blocchi restituito dal metodo getBlocks().

  1. Esegui l'applicazione e dovresti vedere i blocchi sullo schermo:

Screenshot dell&#39;app che mostra i contenuti della sezione &quot;blocks&quot; dei dati JSON.

10. Utilizzare le espressioni di switch

I pattern aggiungono molte funzionalità a switch e case. Per renderli utilizzabili in più punti, Dart dispone di espressioni di switch. Una serie di casi può fornire un valore direttamente a un'istruzione di assegnazione o di ritorno della variabile.

Convertire l'istruzione switch in un'espressione switch

L'analizzatore Dart fornisce assistenza per aiutarti ad apportare modifiche al codice.

  1. Sposta il cursore sull'istruzione switch della sezione precedente.
  2. Fai clic sulla lampadina per visualizzare gli aiuti disponibili.
  3. Seleziona l'assistenza Converti in espressione di switch.

Uno screenshot dell&#39;assistenza &quot;Converti in espressione di switch&quot; disponibile in VS Code.

La nuova versione di questo codice ha il seguente aspetto:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;                                          // Modify from here
    textStyle = switch (block.type) {
      'h1' => Theme.of(context).textTheme.displayMedium,
      'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
      _ => Theme.of(context).textTheme.bodySmall
    };                                                             // to here.

    return Container(
      margin: const EdgeInsets.all(8),
      child: Text(
        block.text,
        style: textStyle,
      ),
    );
  }
}

Un'espressione di switch è simile a un'istruzione switch, ma elimina la parola chiave case e utilizza => per separare il pattern dal corpo della condizione. A differenza delle istruzioni switch, le espressioni switch restituiscono un valore e possono essere utilizzate ovunque sia possibile utilizzare un'espressione.

11. Utilizzare i pattern di oggetti

Dart è un linguaggio orientato agli oggetti, quindi i pattern si applicano a tutti gli oggetti. In questo passaggio, attivi un pattern di oggetti e destruttura le proprietà degli oggetti per migliorare la logica di rendering della data dell'interfaccia utente.

Estrarre le proprietà dai pattern di oggetti

In questa sezione, puoi migliorare la visualizzazione della data dell'ultima modifica utilizzando i pattern.

  • Aggiungi il metodo formatDate a main.dart:

lib/main.dart

String formatDate(DateTime dateTime) {
  final today = DateTime.now();
  final difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: final days, isNegative: true) => '${days.abs()} days ago',
    Duration(inDays: final days) => '$days days from now',
  };
}

Questo metodo restituisce un'espressione di switch che attiva il valore difference, un oggetto Duration. Rappresenta l'intervallo di tempo compreso tra today e il valore modified dei dati JSON.

Ogni caso dell'espressione di switch utilizza un pattern di oggetti che corrisponde chiamando i getter delle proprietà inDays e isNegative dell'oggetto. La sintassi sembra indicare la costruzione di un oggetto Duration, ma in realtà accede ai campi dell'oggetto difference.

I primi tre casi utilizzano i sottopattern costanti 0, 1 e -1 per trovare una corrispondenza con la proprietà dell'oggetto inDays e restituire la stringa corrispondente.

Gli ultimi due casi gestiscono le durate oltre a oggi, ieri e domani:

  • Se la proprietà isNegative corrisponde al pattern costante booleanotrue, ovvero la data di modifica è nel passato, viene visualizzato giorni fa.
  • Se questo caso non rileva la differenza, la durata deve essere un numero positivo di giorni (non è necessario verificare esplicitamente con isNegative: false), quindi la data di modifica è nel futuro e viene visualizzata giorni da oggi.

Aggiungere la logica di formattazione per le settimane

  • Aggiungi due nuovi casi alla funzione di formattazione per identificare le durate superiori a 7 giorni in modo che l'interfaccia utente possa visualizzarle come settimane:

lib/main.dart

String formatDate(DateTime dateTime) {
  final today = DateTime.now();
  final difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: final days) when days > 7 => '${days ~/ 7} weeks from now', // Add from here
    Duration(inDays: final days) when days < -7 =>
      '${days.abs() ~/ 7} weeks ago',                                            // to here.
    Duration(inDays: final days, isNegative: true) => '${days.abs()} days ago',
    Duration(inDays: final days) => '$days days from now',
  };
}

Questo codice introduce le clausole di guardia:

  • Una clausola di guardia utilizza la parola chiave when dopo un pattern di case.
  • Possono essere utilizzati in casi if, istruzioni switch ed espressioni switch.
  • Aggiungono una condizione a un pattern solo dopo la corrispondenza.
  • Se la clausola di guardia restituisce false, l'intero pattern viene smentito e l'esecuzione passa al caso successivo.

Aggiungere la data appena formattata all'interfaccia utente

  1. Infine, aggiorna il metodo build in DocumentScreen per utilizzare la funzione formatDate:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final formattedModifiedDate = formatDate(modified);            // Add this line
    final blocks = document.getBlocks();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Text('Last modified: $formattedModifiedDate'),           // Modify this line
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),
        ],
      ),
    );
  }
}
  1. Esegui il ricaricamento rapido per visualizzare le modifiche nell'app:

Uno screenshot dell&#39;app che mostra la stringa &quot;Ultima modifica: 2 settimane fa&quot; utilizzando la funzione formatDate().

12. Sigillare una classe per il passaggio completo

Tieni presente che non è stato utilizzato un carattere jolly o una modalità predefinita alla fine dell'ultimo switch. Sebbene sia buona norma includere sempre una condizione per i valori che potrebbero non essere presi in considerazione, non è un problema in un esempio semplice come questo, poiché sai che le condizioni che hai definito tengono conto di tutti i valori possibili che inDays potrebbe assumere.

Quando ogni caso in uno switch viene gestito, si parla di switch esaustivo. Ad esempio, l'attivazione di un tipo bool è esaustiva quando ha casi per true e false. L'attivazione di un tipo enum è esaustiva anche se sono presenti casi per ciascuno dei valori dell'enum, perché gli enum rappresentano un numero fisso di valori costanti.

Dart 3 ha esteso il controllo di esattezza agli oggetti e alle gerarchie di classi con il nuovo modificatore di classe sealed. Ristruttura la classe Block come superclasse sigillata.

Crea le sottoclassi

  • In data.dart, crea tre nuove classi (HeaderBlock, ParagraphBlock e CheckboxBlock) che estendono Block:

lib/data.dart

class HeaderBlock extends Block {
  final String text;
  HeaderBlock(this.text);
}

class ParagraphBlock extends Block {
  final String text;
  ParagraphBlock(this.text);
}

class CheckboxBlock extends Block {
  final String text;
  final bool isChecked;
  CheckboxBlock(this.text, this.isChecked);
}

Ognuna di queste classi corrisponde ai diversi valori type del JSON originale: 'h1', 'p' e 'checkbox'.

Sigilla la superclasse

  • Contrassegna la classe Block come sealed. Poi, esegui il refactoring della condizione if come espressione di switch che restituisce la sottoclasse corrispondente a type specificato nel JSON:

lib/data.dart

sealed class Block {
  Block();

  factory Block.fromJson(Map<String, Object?> json) {
    return switch (json) {
      {'type': 'h1', 'text': String text} => HeaderBlock(text),
      {'type': 'p', 'text': String text} => ParagraphBlock(text),
      {'type': 'checkbox', 'text': String text, 'checked': bool checked} =>
        CheckboxBlock(text, checked),
      _ => throw const FormatException('Unexpected JSON format'),
    };
  }
}

La parola chiave sealed è un modificatore di classe, il che significa che puoi estendere o implementare questa classe solo nella stessa libreria. Poiché l'analizzatore conosce i sottotipi di questa classe, segnala un errore se uno switch non ne copre uno e non è esaustivo.

Utilizza un'espressione di switch per visualizzare i widget

  1. Aggiorna la classe BlockWidget in main.dart con un'espressione di switch che utilizza pattern di oggetti per ogni caso:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(8),
      child: switch (block) {
        HeaderBlock(:final text) => Text(
            text,
            style: Theme.of(context).textTheme.displayMedium,
          ),
        ParagraphBlock(:final text) => Text(text),
        CheckboxBlock(:final text, :final isChecked) => Row(
            children: [
              Checkbox(value: isChecked, onChanged: (_) {}),
              Text(text),
            ],
          ),
      },
    );
  }
}

Nella prima versione di BlockWidget, hai attivato un campo di un oggetto Block per restituire un TextStyle. Ora, passa a un'istanza dell'oggetto Block stesso e cerca corrispondenze con i pattern di oggetti che rappresentano i suoi sottoclassi, estraendo le proprietà dell'oggetto durante il processo.

L'analizzatore Dart può verificare che ogni sottoclasse venga gestita nell'espressione di switch perché hai creato Block come classe sigillata.

Tieni inoltre presente che l'utilizzo di un'espressione di switch qui ti consente di passare il risultato direttamente all'elemento child, anziché alla dichiarazione return separata necessaria in precedenza.

  1. Esegui il ricaricamento rapido per vedere i dati JSON della casella di controllo visualizzati per la prima volta:

Uno screenshot dell&#39;app che mostra la casella di controllo &quot;Impara Dart 3&quot;

13. Complimenti

Hai sperimentato con pattern, record, istruzioni switch e case avanzate e classi sigillate. Hai trattato molte informazioni, ma hai appena scalfito la superficie di queste funzionalità. Per ulteriori informazioni sui pattern, consulta la specifica della funzionalità.

I diversi tipi di pattern, i diversi contesti in cui possono apparire e il potenziale nidificazione di pattern secondari rendono le possibilità di comportamento apparentemente infinite. ma sono facili da vedere.

Puoi immaginare tutti i modi possibili per visualizzare i contenuti in Flutter utilizzando i pattern. Utilizzando i pattern, puoi estrarre in sicurezza i dati per creare l'interfaccia utente in poche righe di codice.

Passaggi successivi

  • Consulta la documentazione su pattern, record, istruzioni switch e case avanzate e modificatori di classe nella sezione Lingua della documentazione di Dart.

Documentazione di riferimento

Consulta il codice campione completo, passo passo, nel repository flutter/codelabs.

Per specifiche dettagliate su ogni nuova funzionalità, consulta la documentazione di progettazione originale: