Informazioni su questo codelab
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.
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
- Installa l'SDK Flutter.
- Configura un editor come Visual Studio Code (VS Code).
- 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
- Utilizza il comando
flutter create
per creare un nuovo progetto denominatopatterns_codelab
. Il flag--empty
impedisce la creazione dell'app contatore standard nel filelib/main.dart
, che dovrai comunque rimuovere.
flutter create --empty patterns_codelab
- Quindi, apri la directory
patterns_codelab
utilizzando VS Code.
code patterns_codelab
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.
- 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 widgetScaffold
.
- Per assicurarti che tutto funzioni correttamente, esegui l'app sulla macchina host facendo clic su Esegui ed esegui il debug:
- Per impostazione predefinita, Flutter sceglie la piattaforma di destinazione disponibile. Per modificare la piattaforma di destinazione, seleziona la piattaforma corrente nella barra di stato:
Dovresti vedere un frame vuoto con gli elementi title
e body
definiti nel widget DocumentScreen
:
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 denominatometadata
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
- Nel widget
DocumentScreen
, chiama il metodo gettermetadata
nel metodobuild
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 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 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.
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 inString
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
- Ristruttura il metodo
build
diDocumentScreen
in modo da chiamaremetadata
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
eDateTime 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
.
- 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 dimetadata
nel 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 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 chiavemetadata
._json
non è null._json['metadata']
è anche un tipo di mappa._json['metadata']
contiene le chiavititle
emodified
.title
elocalModified
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'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
, adata.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 classeDocument
.getBlocks()
analizza il JSON in istanze della classeBlock
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 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 sottopattern. Il pattern corrisponde se
block.type
corrisponde a uno dei pattern secondarip
ocheckbox
.
- Il caso finale è uno schema con caratteri jolly,
_
. I caratteri jolly nelle istruzioni switch corrispondono a tutti gli altri. Hanno lo stesso comportamento delle clausoledefault
, 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
.
- Sostituisci il metodo
build
esistente inDocumentationScreen
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()
.
- Esegui l'applicazione e dovresti vedere i blocchi sullo schermo:
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.
- Sposta il cursore sull'istruzione switch della sezione precedente.
- Fai clic sulla lampadina per visualizzare gli aiuti disponibili.
- Seleziona l'assistenza Converti in espressione di switch.
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
amain.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
- Infine, aggiorna il metodo
build
inDocumentScreen
per 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]);
},
),
),
],
),
);
}
}
- Esegui il ricaricamento rapido per visualizzare le modifiche nell'app:
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
eCheckboxBlock
) 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);
}
Ognuna di queste classi corrisponde ai diversi valori type
del JSON originale: 'h1'
, 'p'
e 'checkbox'
.
Sigilla la superclasse
- Contrassegna la classe
Block
comesealed
. Poi, esegui il refactoring della condizione if come espressione di switch che restituisce la sottoclasse corrispondente atype
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
- Aggiorna la classe
BlockWidget
inmain.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.
- Esegui il ricaricamento rapido per vedere i dati JSON della casella di controllo visualizzati per la prima volta:
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: