1. Introduzione
Dart 3 introduce i pattern nel linguaggio, una nuova importante categoria di grammatica. Oltre a questo nuovo modo di scrivere codice Dart, sono stati apportati diversi altri miglioramenti al 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 scelte a tua disposizione quando scrivi codice Dart. In questo codelab imparerai a utilizzarli per rendere il tuo codice più compatto, semplificato e flessibile.
Questo codelab presuppone che tu abbia una certa familiarità con Flutter e Dart. Se ti senti un po' arrugginito, ti consigliamo di ripassare le nozioni di base con le seguenti risorse:
Cosa creerai
Questo codelab crea un'applicazione che mostra un documento JSON in Flutter. L'applicazione simula JSON provenienti da un'origine esterna. Il formato JSON contiene dati del documento come la data di modifica, il titolo, le intestazioni e i paragrafi. Scrivi codice per organizzare i dati in modo ordinato nei record, in modo che possano essere trasferiti e decompressi ovunque i widget Flutter ne abbiano bisogno.
Quindi, utilizzi i pattern per creare il widget appropriato quando il valore corrisponde a quel pattern. Vedrai anche come utilizzare i pattern per destrutturare i dati in variabili locali.

Cosa imparerai a fare
- Come creare un record che memorizza più valori con tipi diversi.
- Come restituire più valori da una funzione utilizzando un record.
- Come utilizzare i pattern per trovare corrispondenze, convalidare e destrutturare i dati di record e altri oggetti.
- Come associare i valori corrispondenti al 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 esaustività per assicurarti che ogni caso venga gestito in un'istruzione switch o in un'espressione switch.
2. Configura l'ambiente
- Installa l'SDK Flutter.
- Configura un editor come Visual Studio Code (VS Code).
- Segui i passaggi della configurazione della piattaforma per almeno una piattaforma di destinazione (iOS, Android, computer o browser web).
3. Crea il progetto
Prima di esaminare pattern, record e altre nuove funzionalità, prenditi un momento per creare un progetto Flutter per cui scrivi tutto il codice.
Crea un progetto Flutter
- Utilizza il comando
flutter createper creare un nuovo progetto denominatopatterns_codelab. Il flag--emptyimpedisce la creazione dell'app contatore standard nel filelib/main.dart, che dovresti comunque rimuovere.
flutter create --empty patterns_codelab
- Poi, apri la directory
patterns_codelabutilizzando VS Code.
code patterns_codelab

Impostare la versione minima dell'SDK
- Imposta il vincolo della versione dell'SDK per il tuo 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.dartche contiene i widget per l'app e - Il file
data.dartche 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 una fonte esterna, come un flusso 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 multiriga nella variabile documentJson.
I dati JSON sono definiti nella classe Document. Più avanti in questo codelab, aggiungerai funzioni che restituiscono 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 nell'ambito della struttura di file Flutter predefinita.
- Per creare un punto di partenza per l'applicazione, sostituisci i contenuti di
main.dartcon 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(),
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 i seguenti due widget all'app:
DocumentAppconfigura l'ultima versione di Material Design per l'applicazione di temi alla UI.DocumentScreenfornisce il layout visivo della pagina utilizzando il widgetScaffold.
- Per assicurarti che tutto funzioni correttamente, esegui l'app sulla macchina host facendo clic su Esegui e debug:

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

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

5. Crea e restituisci record
In questo passaggio, utilizzi i record per restituire più valori da una chiamata di funzione. Poi, chiami questa funzione nel widget DocumentScreen per accedere ai valori e visualizzarli nella UI.
Creare e restituire un record
- In
data.dart, aggiungi un nuovo metodo getter alla classe Document denominatometadatache 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 per 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 campo si chiama modified.
Campi dei record di accesso
- Nel widget
DocumentScreen, chiama il metodo gettermetadatanel metodobuildin modo da poter 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: [ // And the following line.
Center(child: Text('Last modified ${metadataRecord.modified}')),
],
),
);
}
}
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 che compongono il record, puoi utilizzare la sintassi getter integrata dei record.
- Per ottenere un campo posizionale (un campo senza nome, come
title), utilizza il getternel record. Vengono restituiti solo i campi senza nome. - I campi denominati come
modifiednon hanno un getter posizionale, quindi puoi utilizzare direttamente il nome, ad esempiometadataRecord.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
- Esegui il ricaricamento rapido per visualizzare i valori JSON visualizzati nell'app. Il plug-in VS Code Dart esegue il ricaricamento rapido ogni volta che salvi un file.

Puoi vedere che ogni campo ha effettivamente mantenuto il proprio tipo.
- Il metodo
Text()accetta una stringa come primo argomento. - Il campo
modifiedè un DateTime e viene convertito in unStringutilizzando l'interpolazione di stringhe.
L'altro modo type-safe per restituire diversi tipi 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 trasferirli facilmente. Ora, migliora il tuo codice utilizzando i pattern.
Un pattern rappresenta una struttura che può assumere uno o più valori, come un progetto. I pattern vengono confrontati con i valori effettivi per determinare se corrispondono.
Alcuni pattern, quando corrispondono, destrutturano il valore corrispondente estraendone i dati. La destrutturazione consente di estrarre i valori da un oggetto per assegnarli a variabili locali o eseguire ulteriori corrispondenze.
Destrutturare un record in variabili locali
- Esegui il refactoring del metodo
builddiDocumentScreenper chiamaremetadatae 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 from here...
body: Column(children: [Center(child: Text('Last modified $modified'))]),
); // To here.
}
}
Il pattern del record (title, modified: modified) contiene due pattern di variabili che corrispondono ai campi del record restituito da metadata.
- L'espressione corrisponde al sottopattern perché il risultato è un record con due campi, uno dei quali si chiama
modified. - Poiché corrispondono, il pattern di dichiarazione delle variabili destruttura l'espressione, accedendo ai relativi valori e associandoli a nuove variabili locali dello stesso tipo e nome,
String titleeDateTime modified.
Esiste una notazione abbreviata per quando il nome di un campo e la variabile che lo compila sono gli stessi. Refactor the build method of DocumentScreen as follows.
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 è l'abbreviazione di modified: modified. Se vuoi una nuova variabile locale con un nome diverso, puoi scrivere modified: localModified.
- 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 delle variabili che hai utilizzato nell'ultimo passaggio è un pattern irrefutabile: il valore deve corrispondere al pattern, altrimenti si verifica un errore e la destrutturazione non avviene. Pensa a qualsiasi 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 con cui vengono confrontati non corrispondano.
- Sono progettate per influenzare il flusso di controllo, in base alla corrispondenza o meno del valore.
- Se non corrispondono, non interrompono l'esecuzione con un errore, ma passano 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 vedere come i pattern possono aiutarti a lavorare con i dati JSON.
- Sostituisci la versione precedente di
metadatacon una che legge i valori dalla mappa_json. Copia e incolla questa versione dimetadatanel corsoDocument:
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 di pattern per eseguire la stessa convalida utilizzando meno codice. Esegue tre controlli prima di fare qualsiasi altra cosa:
- Il JSON contiene la struttura dei dati che ti aspetti:
if (_json.containsKey('metadata')) - I dati hanno il tipo che ti aspetti:
if (metadataJson is Map) - Che i dati non siano nulli, 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
metadatacon questo codice:
lib/data.dart
class Document {
final Map<String, Object?> _json;
Document() : _json = jsonDecode(documentJson);
(String, {DateTime modified}) get metadata {
if (_json case { // Modify from here...
'metadata': {'title': String title, 'modified': String localModified},
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
} // to here.
}
}
Qui vedi un nuovo tipo di istruzione if (introdotta in Dart 3), l'if-case. Il corpo del caso viene eseguito solo se il pattern del caso 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._jsoncontiene una chiavemetadata._jsonnon è null._json['metadata']è anche un tipo di mappa._json['metadata']contiene le chiavititleemodified.titleelocalModifiedsono stringhe e non sono null.
Se il valore non corrisponde, il pattern rifiuta (si rifiuta di continuare l'esecuzione) e passa alla clausola else. Se la corrispondenza ha esito positivo, 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 delle specifiche della funzionalità.
8. Preparare l'app per altri pattern
Finora, hai gestito la parte metadata dei dati JSON. In questo passaggio, perfezioni ulteriormente 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, adata.dart, che viene utilizzata per leggere e archiviare 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 lo stesso if-case con un pattern di mappa che hai già visto.
Vedrai che i dati JSON hanno l'aspetto del pattern previsto, anche se contengono un'informazione aggiuntiva chiamata checked che non è presente nel pattern. Questo perché quando utilizzi questi tipi di pattern (chiamati "pattern di mappatura"), vengono presi in considerazione solo gli elementi specifici che hai definito nel pattern e vengono ignorati tutti gli altri dati.
Restituisce un elenco di oggetti Block
- Successivamente, aggiungi una nuova funzione,
getBlocks(), alla classeDocument.getBlocks()analizza il JSON in istanze della classeBlocke restituisce un elenco di blocchi da visualizzare nella UI:
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 utilizzi in un secondo momento per creare la UI. Un'istruzione if-case familiare esegue la convalida e trasforma il valore dei metadati blocks in un nuovo List denominato blocksJson (senza pattern, sarebbe necessario il metodo toList() per la conversione).
Il valore letterale dell'elenco contiene una raccolta per riempire il nuovo elenco con oggetti Block.
Questa sezione non introduce funzionalità relative ai pattern che non hai già provato in questo codelab. Nel passaggio successivo, ti prepari a eseguire il rendering degli elementi dell'elenco nella tua UI.
9. Utilizzare i pattern per visualizzare il documento
Ora hai destrutturato e ricomposto correttamente i dati JSON utilizzando un'istruzione if-case e pattern refutabili. Ma if-case è solo uno dei miglioramenti alle strutture di controllo del flusso che vengono forniti con i pattern. Ora applichi le tue conoscenze sui pattern confutabili alle istruzioni switch.
Controllare il rendering utilizzando pattern con istruzioni switch
- In
main.dart, crea un nuovo widget,BlockWidget, che determina lo stile di ogni blocco in base al relativo campotype.
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.
- La prima istruzione CASE utilizza un pattern di stringa costante. Il pattern corrisponde se
block.typeè uguale al valore costanteh1. - La seconda istruzione CASE utilizza un pattern OR logico con due pattern di stringhe costanti come sottopatern. Il pattern corrisponde se
block.typecorrisponde a uno dei sottopaternpocheckbox.
- L'ultimo caso è un pattern jolly,
_. I caratteri jolly nei casi switch corrispondono a tutto il resto. Si comportano come le clausoledefault, che sono ancora consentite nelle istruzioni switch (sono solo un po' più dettagliate).
I pattern con caratteri jolly possono essere utilizzati ovunque sia consentito un pattern, ad esempio in un pattern di dichiarazione di variabile: var (title, _) = document.metadata;
In questo contesto, il carattere jolly non associa alcuna variabile. Il secondo campo viene ignorato.
Nella sezione successiva, scoprirai altre funzionalità di switch dopo aver visualizzato gli oggetti Block.
Visualizzare i contenuti del documento
Crea una variabile locale che contenga l'elenco degli oggetti Block chiamando getBlocks() nel metodo build del widget DocumentScreen.
- Sostituisci il metodo
buildesistente inDocumentationScreencon 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().
- Esegui l'applicazione e dovresti vedere i blocchi sullo schermo:

10. Utilizzare le espressioni switch
I pattern aggiungono molte funzionalità a switch e case. Per renderle utilizzabili in più posizioni, Dart dispone di espressioni switch. Una serie di casi può fornire un valore direttamente a un'istruzione di assegnazione di una variabile o di restituzione.
Converti l'istruzione switch in un'espressione switch
L'analizzatore Dart fornisce assistenza per aiutarti ad apportare modifiche al codice.
- Sposta il cursore sull'istruzione switch della sezione precedente.
- Fai clic sull'icona a forma di lampadina per visualizzare gli aiuti disponibili.
- Seleziona l'assistente Converti in espressione switch.

La nuova versione di questo codice ha questo 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 switch è simile a un'istruzione switch, ma elimina la parola chiave case e utilizza => per separare il pattern dal corpo del case. 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 degli 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 destrutturi le proprietà degli oggetti per migliorare la logica di rendering della data della tua UI.
Estrarre proprietà dai pattern di oggetti
In questa sezione, migliorerai la visualizzazione della data dell'ultima modifica utilizzando i pattern.
- Aggiungi il metodo
formatDateamain.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 switch che attiva il valore difference, un oggetto Duration. Rappresenta l'intervallo di tempo tra today e il valore modified dei dati JSON.
Ogni caso dell'espressione switch utilizza un pattern di oggetto che corrisponde chiamando i getter delle proprietà dell'oggetto inDays e isNegative. La sintassi sembra creare 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 durate oltre oggi, ieri e domani:
- Se la proprietà
isNegativecorrisponde al pattern costante booleanotrue, il che significa che la data di modifica è passata, 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 è futura e viene visualizzato 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 protezione:
- Una clausola di protezione utilizza la parola chiave
whendopo un pattern di caso. - Possono essere utilizzati in istruzioni if, switch e switch expression.
- Aggiungono una condizione a un pattern solo dopo che è stato trovato una corrispondenza.
- Se la clausola di protezione restituisce il valore false, l'intero pattern viene confutato e l'esecuzione passa al caso successivo.
Aggiungi la data appena formattata alla UI
- Infine, aggiorna il metodo
buildinDocumentScreenper utilizzare la funzioneformatDate:
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]);
},
),
),
],
),
);
}
}
- Ricarica a caldo per visualizzare le modifiche nell'app:

12. Sigillare una classe per il cambio esaustivo
Tieni presente che non hai utilizzato un carattere jolly o un caso predefinito alla fine dell'ultimo switch. Anche se è buona norma includere sempre un caso per i valori che potrebbero non essere inclusi, in un esempio semplice come questo è accettabile, poiché sai che i casi che hai definito tengono conto di tutti i valori possibili che inDays potrebbe potenzialmente assumere.
Quando ogni caso in un'istruzione 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 quando sono presenti casi per ciascuno dei valori dell'enumerazione, perché le enumerazioni rappresentano un numero fisso di valori costanti.
Dart 3 ha esteso il controllo di esaustività a oggetti e gerarchie di classi con il nuovo modificatore di classe sealed. Refattorizza la classe Block come superclasse sigillata.
Crea le sottoclassi
- In
data.dart, crea tre nuove classi,HeaderBlock,ParagraphBlockeCheckboxBlock, che estendonoBlock:
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);
}
Ciascuna di queste classi corrisponde ai diversi valori type del JSON originale: 'h1', 'p' e 'checkbox'.
Sigillare la superclasse
- Contrassegna la classe
Blockcomesealed. Poi, esegui il refactoring dell'istruzione if-case come espressione switch che restituisce la sottoclasse corrispondente atypespecificato nel file 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 che indica che puoi estendere o implementare questa classe solo nella stessa libreria. Poiché l'analizzatore conosce i sottotipi di questa classe, segnala un errore se un'istruzione switch non copre uno di questi sottotipi e non è esaustiva.
Utilizzare un'espressione switch per visualizzare i widget
- Aggiorna la classe
BlockWidgetinmain.dartcon un'espressione 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, passi a un'istanza dell'oggetto Block e la confronti con i pattern di oggetti che rappresentano le relative sottoclassi, estraendo le proprietà dell'oggetto durante il processo.
L'analizzatore Dart può verificare che ogni sottoclasse venga gestita nell'espressione switch perché hai creato Block una classe sigillata.
Tieni presente inoltre che l'utilizzo di un'espressione switch qui ti consente di passare il risultato direttamente all'elemento child, anziché all'istruzione return separata necessaria in precedenza.
- Ricarica rapida per visualizzare per la prima volta i dati JSON della casella di controllo:

13. Complimenti
Hai sperimentato correttamente pattern, record, switch ed case avanzati e classi sigillate. Hai trattato molte informazioni, ma hai solo scalfito la superficie di queste funzionalità. Per ulteriori informazioni sui pattern, consulta le specifiche della funzionalità.
I diversi tipi di pattern, i diversi contesti in cui possono apparire e il potenziale annidamento dei sottopatern rendono le possibilità di comportamento apparentemente infinite. ma sono facili da vedere.
Puoi immaginare tutti i modi per visualizzare i contenuti in Flutter utilizzando i pattern. Utilizzando i pattern, puoi estrarre in modo sicuro i dati per creare la tua UI in poche righe di codice.
Passaggi successivi
- Consulta la documentazione su pattern, record, switch e casi avanzati e modificatori di classe nella sezione Language della documentazione di Dart.
Documentazione di riferimento
Visualizza il codice campione completo, passo dopo passo, nel repository flutter/codelabs.
Per specifiche dettagliate di ogni nuova funzionalità, consulta i documenti di progettazione originali: