1. Introduzione
Dart 3 introduce i pattern nella lingua, una nuova categoria di grammatica. Oltre a questo nuovo modo di scrivere il codice Dart, sono stati apportati diversi altri miglioramenti al linguaggio, tra cui:
- record per raggruppare dati di diversi tipi,
- modificatori di classe per controllare l'accesso; e
- nuove espressioni di switch e istruzioni if-case.
Queste funzioni ampliano le scelte a tua disposizione quando scrivi il 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, considera 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 proveniente da un'origine esterna. Il file JSON contiene dati di documenti come la data di modifica, il titolo, le intestazioni e i paragrafi. Scrivi il codice per pacchettizzare i dati in modo ordinato nei record, in modo che possano essere trasferiti e scomposti ovunque i tuoi widget Flutter ne abbiano bisogno.
Puoi quindi usare i pattern per creare il widget appropriato quando il valore corrisponde a quel pattern. Vedrai anche come utilizzare i pattern per suddividere i dati in variabili locali.
Cosa imparerai a fare
- Come creare un record per archiviare più valori di tipi diversi.
- Come restituire più valori da una funzione utilizzando un record.
- Come utilizzare i pattern per abbinare, convalidare e destrutturare i dati di record e altri oggetti.
- Come associare i valori di corrispondenza del pattern a variabili nuove o esistenti.
- Come utilizzare le nuove funzionalità di cambio delle istruzioni, le espressioni di cambio e le istruzioni if-case.
- Come sfruttare il controllo di completezza per garantire che ogni richiesta venga gestita in un'istruzione o un'espressione switch.
2. Configura l'ambiente
- Installa l'SDK Flutter.
- Configura un editor, ad esempio Visual Studio Code (VS Code).
- Segui i passaggi di configurazione della piattaforma per almeno una piattaforma di destinazione (iOS, Android, desktop o un browser web).
3. Creare il progetto
Prima di dedicarti a pattern, record e altre nuove funzionalità, dedica un momento alla creazione di un semplice progetto Flutter per il quale scrivi tutto il codice.
Crea un progetto Flutter
- Usa 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 minima dell'SDK
- Imposta il vincolo di versione dell'SDK in modo che il progetto dipenda da Dart 3 o versioni successive.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Configurare il progetto
In questo passaggio, crei o aggiorni due file Dart:
- Il file
main.dart
che contiene 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 al suo interno il codice seguente:
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 sorgente esterna, come uno stream I/O o una richiesta HTTP. In questo codelab, semplificherai questo caso d'uso più realistico simulando i dati JSON in arrivo con una stringa multiriga 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
come parte della struttura predefinita del file Flutter.
- 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 i seguenti due widget all'app:
DocumentApp
configura la versione più recente di Material Design per i temi della UI.DocumentScreen
fornisce il layout visivo della pagina utilizzando il widgetScaffold
.
- Per assicurarti che tutto funzioni correttamente, esegui l'app sul computer host facendo clic su Esegui e debug:
- Per impostazione predefinita, Flutter sceglie la piattaforma di destinazione disponibile. Per cambiare 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. Creare e restituire i record
In questo passaggio, utilizzerai i record per restituire più valori da una chiamata di funzione. Quindi, 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 restituisca 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 di ritorno genera 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
in 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: [
Center(
child: Text(
'Last modified ${metadataRecord.modified}', // And this one.
),
),
],
),
);
}
}
Il metodo getter metadata
restituisce un record assegnato alla variabile locale metadataRecord
. Le registrazioni 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 dal record, puoi utilizzare la sintassi getter integrata.
- Per ottenere un campo posizionale (un campo senza nome, come
title
), utilizza il getter$<num>
nel record. Vengono restituiti solo i campi senza nome. - I campi con nome come
modified
non hanno un getter posizionale, quindi puoi usarne il nome direttamente, ad esempiometadataRecord.modified
.
Per determinare il nome di un getter per un campo posizionale, inizia da $1
e salta i campi con nome. Ad esempio:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Ricarica a caldo per vedere i valori JSON visualizzati nell'app. Il plug-in VS Code Dart si ricarica a ogni salvataggio di un file.
Puoi vedere che, infatti, ogni campo ha mantenuto il proprio tipo.
- Il metodo
Text()
prende una stringa come primo argomento. - Il campo
modified
è di tipo DateTime e viene convertito in un valoreString
tramite l'interpolazione di stringhe.
L'altro modo sicuro per il tipo di restituire diversi tipi di dati è definire una classe, che è più dettagliata.
6. Abbina e destruttura con pattern
I record possono raccogliere in modo efficiente diversi tipi di dati e trasmetterli facilmente. Ora migliora il codice usando i pattern.
Un pattern rappresenta una struttura che uno o più valori possono assumere, come uno schema. I pattern vengono confrontati con i valori effettivi per determinare se corrispondono.
Alcuni pattern, quando corrispondono, destrutturano il valore corrispondente estraendo i dati dal valore corrispondente. La distruzione ti consente di separare i valori da un oggetto per assegnarli a variabili locali o per eseguire ulteriori corrispondenze con questi valori.
Destruttura un record in variabili locali
- Esegui il refactoring del metodo
build
diDocumentScreen
per chiamaremetadata
e utilizzarlo per inizializzare una dichiarazione delle variabili 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 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 è 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 un modo breve per indicare 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 della variabile :modified
è un'abbreviazione di modified: modified
. Se vuoi una nuova variabile locale con un nome diverso, puoi scrivere modified: localModified
.
- Ricarica a caldo per visualizzare lo stesso risultato del passaggio precedente. Il comportamento è esattamente lo stesso: hai appena reso il codice più conciso.
7. Utilizza 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 confusi.
Il pattern di dichiarazione delle variabili che hai utilizzato nell'ultimo passaggio è un pattern inconfutabile: il valore deve corrispondere al pattern altrimenti si tratta di un errore e la destrutturazione non avverrà. Pensa a eventuali dichiarazioni o assegnazioni di variabili. non è possibile assegnare un valore a una variabile se non sono dello stesso tipo.
I pattern confutabili, invece, vengono utilizzati in contesti di flusso di controllo:
- Si aspetta che alcuni valori con cui confrontano non corrispondano.
- Il loro scopo è influenzare il 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 valori JSON senza pattern
In questa sezione leggerai i dati senza corrispondenza di pattern per vedere come i pattern possono aiutarti a lavorare con i dati JSON.
- Sostituisci la versione precedente di
metadata
con una che legge i valori della mappa_json
. Copia e incolla questa versione dimetadata
nella classeDocument
:
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 del pattern per eseguire la stessa convalida utilizzando meno codice. Esegue tre controlli prima di svolgere qualsiasi altra attività:
- Il file JSON contiene la struttura dei dati prevista:
if (_json.containsKey('metadata'))
- I dati sono del tipo previsto:
if (metadataJson is Map)
- I dati non sono nulli, il che è implicitamente confermato nel controllo precedente.
Leggere i valori JSON utilizzando un pattern di mappa
Con un pattern confutato, puoi verificare che il file 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 puoi vedere un nuovo tipo di istruzione if (introdotta in Dart 3), 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 arrivo. 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 sono null.
Se il valore non corrisponde, il pattern respinge (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. Prepara l'app per altri pattern
Finora, affronti la parte metadata
dei dati JSON. In questo passaggio, perfezionirai 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 per l'archiviazione dei dati
- Aggiungi a
data.dart
una nuova classe,Block
, che viene utilizzata per leggere e archiviare i dati per 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 della fabbrica fromJson()
utilizza lo stesso if-case con un pattern di mappa che hai visto prima.
Nota che json
corrisponde al pattern della mappa, anche se una delle chiavi, checked
, non viene considerata nel pattern. I pattern della mappa ignorano le voci nell'oggetto della mappa che non sono esplicitamente prese in considerazione nel pattern.
Restituisce un elenco di oggetti Block
- Successivamente, aggiungi una nuova funzione,
getBlocks()
, alla classeDocument
.getBlocks()
analizza il codice JSON nelle istanze della classeBlock
e 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 utilizzerai in seguito per creare la UI. Un'istruzione if-case familiare esegue la convalida e trasmette il valore dei metadati blocks
in un nuovo List
denominato blocksJson
(senza pattern, sarebbe necessario il metodo toList()
per trasmettere).
Il valore letterale elenco contiene una raccolta per per riempire il nuovo elenco con Block
oggetti.
Questa sezione non introduce funzionalità relative ai pattern che non hai già provato in questo codelab. Nel passaggio successivo, ti preparerai per eseguire il rendering degli elementi dell'elenco nella tua UI.
9. Utilizza i pattern per visualizzare il documento
Ora puoi destrutturare e ricomporre correttamente i dati JSON utilizzando un'istruzione if-case e pattern confutabili. Tuttavia, l'approccio "if-case" è solo uno dei miglioramenti al controllo delle strutture di flusso che presentano pattern. Ora, puoi applicare le tue conoscenze dei pattern confutabili per cambiare istruzione.
Controllare ciò che viene visualizzato utilizzando pattern con istruzioni di switch
- In
main.dart
, crea un nuovo widget,BlockWidget
, che determina lo stile di ogni blocco in base al 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 stringhe costanti. Il pattern corrisponde se
block.type
è uguale al valore costanteh1
. - La seconda istruzione case utilizza un pattern logico con due pattern di stringhe costanti come sottopattern. Il pattern corrisponde se
block.type
corrisponde a uno dei pattern secondarip
ocheckbox
.
- L'ultimo caso è un pattern con caratteri jolly,
_
. I caratteri jolly nelle richieste di cambio corrispondono a tutto il resto. Si comportano come le clausoledefault
, che sono comunque 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 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 passaggio dopo aver visualizzato gli oggetti Block
.
Visualizza i contenuti del documento
Crea una variabile locale contenente l'elenco di oggetti Block
richiamando 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 nell'elenco di blocchi restituiti dal metodo getBlocks()
.
- Esegui l'applicazione. Dovresti visualizzare i blocchi sullo schermo:
10. Utilizzare espressioni di cambio
I pattern aggiungono molte funzionalità a switch
e case
. Per renderli utilizzabili in più luoghi, Dart dispone di espressioni di cambio. Una serie di casi può fornire un valore direttamente a un'assegnazione di variabile o a un'istruzione di ritorno.
Converti l'istruzione switch in un'espressione switch
Lo strumento di analisi 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 eventi assistiti disponibili.
- Seleziona l'opzione Converti in cambia espressione.
La nuova versione di questo codice è simile alla seguente:
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 sembra simile a un'istruzione switch, ma elimina la parola chiave case
e utilizza =>
per separare il pattern dal corpo della richiesta. A differenza delle istruzioni switch, le espressioni switch restituiscono un valore e possono essere utilizzate ovunque sia possibile usare un'espressione.
11. Utilizza pattern degli oggetti
Dart è un linguaggio orientato agli oggetti, quindi i pattern si applicano a tutti gli oggetti. In questo passaggio, attivi un pattern degli oggetti e destruttura le proprietà degli oggetti per migliorare la logica di rendering della data della tua UI.
Estrarre le proprietà dai pattern degli oggetti
In questa sezione migliorerai la modalità di 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 tra today
e il valore modified
dai dati JSON.
Ogni caso dell'espressione switch utilizza un pattern dell'oggetto che corrisponde alla chiamata dei getter sulle 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
in modo che corrispondano alla proprietà dell'oggetto inDays
e restituiscano la stringa corrispondente.
Gli ultimi due casi gestiscono durate che vanno oltre oggi, ieri e domani:
- Se la proprietà
isNegative
corrisponde al pattern della costante booleanatrue
, ovvero la data di modifica è nel passato, viene visualizzata giorni prima. - Se questo caso non rileva la differenza, la durata deve essere un numero positivo di giorni (non è necessario eseguire una verifica esplicita con
isNegative: false
), quindi la data di modifica è nel futuro e mostra i giorni a partire da ora.
Aggiungi logica di formattazione per le settimane
- Aggiungi due nuovi casi alla funzione di formattazione per identificare durate superiori a 7 giorni in modo che nella UI possano essere visualizzate 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
when
dopo un pattern di maiuscole e minuscole. - Possono essere utilizzate in "if-case" e per "switch" con le istruzioni e viceversa.
- ma aggiungono una condizione a un pattern solo dopo aver trovato una corrispondenza.
- Se la clausola di protezione restituisce un 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
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]);
},
),
),
],
),
);
}
}
- Ricarica a caldo per vedere le modifiche nella tua app:
12. Sigilla una classe per un passaggio completo
Nota che non hai utilizzato un carattere jolly o una combinazione di maiuscole e minuscole predefinita alla fine dell'ultima opzione. Sebbene sia buona norma includere sempre un caso per i valori che potrebbero non essere individuati, è normale usare un esempio semplice come questo, poiché sai che i casi che definisci prendono in considerazione tutti i possibili valori inDays
.
Quando ogni caso in un passaggio viene gestito, si parla di cambio esaustivo. Ad esempio, l'attivazione di un tipo bool
è esaustivo quando si verificano casi per true
e false
. L'attivazione di un tipo enum
è esaustivo quando esistono anche casi per ogni valore dell'enumerazione, perché le enumerazioni rappresentano un numero fisso di valori costanti.
Dart 3 ha esteso il controllo dell'esaustività a oggetti e gerarchie di classi con il nuovo modificatore di classe sealed
. Esegui il refactoring della classe Block
come superclasse sigillata.
crea le sottoclassi
- In
data.dart
, crea tre nuovi corsi (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 file JSON originale: 'h1'
, 'p'
e 'checkbox'
.
Sigilla la superclasse
- Contrassegna il corso
Block
comesealed
. Quindi, esegui il refactoring dell'elemento if-case come un'espressione di switch che restituisce la sottoclasse corrispondente al valoretype
specificato 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, il che significa che puoi solo estendere o implementare questa classe nella stessa libreria. Poiché l'analizzatore conosce i sottotipi di questa classe, segnala un errore se uno switch non riesce a coprire uno di questi e non è esaustivo.
Utilizzare un'espressione di switch per visualizzare i widget
- Aggiorna la classe BlockWidget in
main.dart
con un'espressione di switch che utilizza i pattern degli oggetti per ciascun 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 tua prima versione di BlockWidget
, hai attivato un campo di un oggetto Block
per restituire un TextStyle
. Ora puoi cambiare un'istanza dell'oggetto Block
stesso e creare una corrispondenza con i pattern degli oggetti che rappresentano le sottoclassi, estraendo le proprietà dell'oggetto nel processo.
L'analizzatore Dart può verificare che ogni sottoclasse sia gestita nell'espressione di switch perché hai reso Block
una classe protetta.
Tieni inoltre presente che l'utilizzo di un'espressione switch qui ti consente di passare il risultato direttamente all'elemento child
, a differenza dell'istruzione di ritorno separata necessaria in precedenza.
- Ricarica a caldo per visualizzare i dati JSON della casella di controllo per la prima volta:
13. Complimenti
Hai sperimentato correttamente pattern, record, opzioni e custodie migliorate e classi sigillate. Hai trattato molte informazioni, ma hai appena scalfito la superficie di queste caratteristiche. Per ulteriori informazioni sui pattern, consulta la specifica delle caratteristiche.
I diversi tipi di pattern, i diversi contesti in cui possono comparire e la potenziale nidificazione di sottopattern rendono le possibilità di comportamento apparentemente infinite. Ma sono facili da vedere.
Puoi immaginare diversi modi per visualizzare i contenuti in Flutter utilizzando i motivi. Utilizzando i pattern, puoi estrarre in modo sicuro i dati per creare la tua UI con poche righe di codice.
Passaggi successivi
- Consulta la documentazione relativa a pattern, record, opzioni e casi avanzati e modificatori di classe nella sezione Lingua della documentazione di Dart.
Documenti di riferimento
Visualizza il codice di esempio completo, passo dopo passo, nel repository flutter/codelabs
.
Per le specifiche dettagliate di ogni nuova funzionalità, consulta la documentazione sul design originale: