1. Einführung
Dart 3 führt der Sprache Muster ein, eine wichtige neue Kategorie der Grammatik. Neben dieser neuen Möglichkeit zum Schreiben von Dart-Code gibt es noch weitere Sprachverbesserungen, darunter:
- records zum Bündeln von Daten verschiedener Typen,
- Klassenmodifikatoren zur Zugriffssteuerung.
- neue Wechselausdrücke und if-case-Anweisungen.
Diese Funktionen erweitern die Auswahlmöglichkeiten beim Schreiben von Dart-Code. In diesem Codelab erfahren Sie, wie Sie damit Ihren Code kompakter, rationalisierter und flexibler gestalten.
In diesem Codelab wird vorausgesetzt, dass Sie mit Flutter und Dart vertraut sind. Wenn Sie das Gefühl haben, etwas eingerostet zu sein, können Sie die Grundlagen mithilfe der folgenden Ressourcen noch einmal auffrischen:
Aufgaben
In diesem Codelab wird eine Anwendung erstellt, mit der ein JSON-Dokument in Flutter angezeigt wird. Die Anwendung simuliert JSON aus einer externen Quelle. Die JSON-Datei enthält Dokumentdaten wie das Änderungsdatum, den Titel, die Überschriften und die Absätze. Sie schreiben Code, um Daten ordentlich in Datensätze zu verpacken, damit sie dort übertragen und entpackt werden können, wo Ihre Flutter-Widgets sie benötigen.
Sie verwenden dann Muster, um das entsprechende Widget zu erstellen, wenn der Wert mit diesem Muster übereinstimmt. Außerdem erfahren Sie, wie Sie mithilfe von Mustern Daten in lokale Variablen destrukturieren.
Aufgaben in diesem Lab
- Datensatz erstellen, der mehrere Werte mit unterschiedlichen Typen speichert
- So geben Sie mehrere Werte aus einer Funktion mithilfe eines Eintrags zurück.
- Hier erfahren Sie, wie Sie mithilfe von Mustern Daten aus Datensätzen und anderen Objekten abgleichen, validieren und destrukturieren.
- Mit dem Muster übereinstimmende Werte an neue oder vorhandene Variablen binden.
- Hier erfahren Sie, wie Sie neue Funktionen für Switch-Anweisungen sowie Switch-Ausdrücke und if-Case-Anweisungen verwenden.
- Hier erfahren Sie, wie Sie die Vollständigkeitsprüfung nutzen, um sicherzustellen, dass jeder Fall in einer Switch-Anweisung oder einem Switch-Ausdruck verarbeitet wird.
2. Umgebung einrichten
- Installieren Sie das Flutter SDK.
- Richten Sie einen Editor wie Visual Studio Code (VS Code) ein.
- Führen Sie die Schritte zur Plattformeinrichtung für mindestens eine Zielplattform aus (iOS, Android, Computer oder Webbrowser).
3. Projekt erstellen
Bevor Sie sich mit Mustern, Datensätzen und anderen neuen Funktionen befassen, nehmen Sie sich einen Moment Zeit, um ein einfaches Flutter-Projekt zu erstellen, für das Sie Ihren gesamten Code schreiben.
Flutter-Projekt erstellen
- Verwenden Sie den Befehl
flutter create
, um ein neues Projekt mit dem Namenpatterns_codelab
zu erstellen. Das Flag--empty
verhindert, dass die Standard-Zähler-App in der Dateilib/main.dart
erstellt wird. Diese muss in jedem Fall entfernt werden.
flutter create --empty patterns_codelab
- Öffnen Sie dann das Verzeichnis
patterns_codelab
mit VS Code.
code patterns_codelab
Mindestversion des SDK festlegen
- Legen Sie die SDK-Versionseinschränkung für Ihr Projekt so fest, dass sie von Dart 3 oder höher abhängig ist.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Projekt einrichten
In diesem Schritt erstellen oder aktualisieren Sie zwei Dart-Dateien:
- Die Datei
main.dart
, die Widgets für die App enthält - Die Datei
data.dart
, die die Daten der Anwendung enthält.
In den nachfolgenden Schritten werden Sie beide Dateien weiter ändern.
Daten für die App definieren
- Erstellen Sie eine neue Datei mit dem Namen
lib/data.dart
und fügen Sie den folgenden Code hinzu:
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"
}
]
}
''';
Stellen Sie sich ein Programm vor, das Daten von einer externen Quelle empfängt, z. B. einem E/A-Stream oder einer HTTP-Anfrage. In diesem Codelab vereinfachen Sie diesen Anwendungsfall, indem Sie eingehende JSON-Daten mit einem mehrzeiligen String in der Variablen documentJson
simulieren.
Die JSON-Daten werden in der Klasse Document
definiert. In diesem Codelab fügen Sie später Funktionen hinzu, die Daten aus dem geparsten JSON-Code zurückgeben. Diese Klasse definiert und initialisiert das Feld _json
in seinem Konstruktor.
Anwendung ausführen
Mit dem Befehl flutter create
wird die Datei lib/main.dart
als Teil der standardmäßigen Flutter-Dateistruktur erstellt.
- Ersetzen Sie den Inhalt von
main.dart
durch den folgenden Code, um einen Startpunkt für die Anwendung zu erstellen:
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'),
),
],
),
);
}
}
Sie haben der App die folgenden zwei Widgets hinzugefügt:
DocumentApp
richtet die neueste Version von Material Design für die Gestaltung der Benutzeroberfläche ein.DocumentScreen
stellt mithilfe desScaffold
-Widgets das visuelle Layout der Seite bereit.
- Um sicherzustellen, dass alles reibungslos funktioniert, führen Sie die App auf Ihrem Hostcomputer aus. Klicken Sie dazu auf Ausführen und Fehler beheben:
- Standardmäßig wählt Flutter die verfügbare Zielplattform aus. Um die Zielplattform zu ändern, wählen Sie die aktuelle Plattform in der Statusleiste aus:
Sie sollten einen leeren Frame mit den im DocumentScreen
-Widget definierten Elementen title
und body
sehen:
5. Datensätze erstellen und zurückgeben
In diesem Schritt verwenden Sie Datensätze, um mehrere Werte aus einem Funktionsaufruf zurückzugeben. Anschließend rufen Sie diese Funktion im DocumentScreen
-Widget auf, um auf die Werte zuzugreifen und sie in der Benutzeroberfläche widerzuspiegeln.
Datensatz erstellen und zurückgeben
- Fügen Sie in
data.dart
eine neue Getter-Methode mit dem Namenmetadata
zur Dokumentklasse hinzu, die einen Eintrag zurückgibt:
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.
}
Der Rückgabetyp dieser Funktion ist ein Datensatz mit zwei Feldern, eines mit dem Typ String
und eines mit dem Typ DateTime
.
Mit der Rückgabeanweisung wird ein neuer Datensatz erstellt, indem die beiden Werte in Klammern ((title, modified: now)
) eingeschlossen sind.
Das erste Feld ist positionell und unbenannt, das zweite Feld hat den Namen modified
.
Eintragsfelder aufrufen
- Rufen Sie im
DocumentScreen
-Widget die Getter-Methodemetadata
in derbuild
-Methode auf, damit Sie Ihren Eintrag abrufen und auf seine Werte zugreifen können:
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.
),
),
],
),
);
}
}
Die Getter-Methode metadata
gibt einen Datensatz zurück, der der lokalen Variablen metadataRecord
zugewiesen ist. Datensätze sind eine einfache Möglichkeit, mehrere Werte aus einem einzelnen Funktionsaufruf zurückzugeben und sie einer Variablen zuzuweisen.
Um auf die einzelnen Felder in diesem Datensatz zuzugreifen, verwenden Sie den Eintrag „records“ mit der integrierten Getter-Syntax.
- Verwenden Sie den Getter
$<num>
für den Datensatz, um ein Positionsfeld (ein Feld ohne Namen wietitle
) abzurufen. Dadurch werden nur unbenannte Felder zurückgegeben. - Benannte Felder wie
modified
haben keinen Positions-Getter, sodass Sie den Namen direkt verwenden können, z. B.metadataRecord.modified
.
Um den Namen eines Getters für ein Positionsfeld zu ermitteln, beginnen Sie mit $1
und überspringen benannte Felder. Beispiel:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Hot Refresh, um die JSON-Werte zu sehen, die in der App angezeigt werden. Das VS Code Dart-Plug-in wird jedes Mal, wenn Sie eine Datei speichern, per Hot Refresh geladen.
Wie Sie sehen, hat jedes Feld seinen Typ beibehalten.
- Die Methode
Text()
verwendet einen String als erstes Argument. - Das Feld
modified
ist ein Datum/Uhrzeit und wird mithilfe von Stringinterpolation in einString
umgewandelt.
Die andere typsichere Möglichkeit, verschiedene Datentypen zurückzugeben, besteht darin, eine Klasse zu definieren, die ausführlicher ist.
6. Mit Mustern abgleichen und destrukturieren
Aufzeichnungen können effizient verschiedene Arten von Daten erfassen und einfach weitergeben. Verbessern Sie nun Ihren Code mithilfe von Mustern.
Ein Muster stellt eine Struktur dar, die ein oder mehrere Werte annehmen können, wie ein Bauplan. Muster werden mit tatsächlichen Werten verglichen, um festzustellen, ob sie übereinstimmen.
Bei einer Übereinstimmung wird der übereinstimmende Wert bei einigen Mustern desstrukturiert, indem Daten daraus abgerufen werden. Durch das Löschen können Sie Werte aus einem Objekt entpacken, um sie lokalen Variablen zuzuweisen oder einen weiteren Abgleich durchzuführen.
Datensatz in lokale Variablen destrukturieren
- Refaktorieren Sie die Methode
build
vonDocumentScreen
, ummetadata
aufzurufen, und verwenden Sie sie zum Initialisieren einer Deklaration der Mustervariablen:
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
),
),
],
),
);
}
}
Das Eintragsmuster (title, modified: modified)
enthält zwei Variablenmuster, die mit den Feldern des Eintrags abgleichen, die von metadata
zurückgegeben wurden.
- Der Ausdruck stimmt mit dem Submuster überein, da das Ergebnis ein Datensatz mit zwei Feldern ist, von denen eines den Namen
modified
hat. - Da sie übereinstimmen, destrukturiert das Variablendeklarationsmuster den Ausdruck, indem es auf seine Werte zugreift und sie an neue lokale Variablen derselben Typen und Namen,
String title
undDateTime modified
, bindet.
Es gibt eine Abkürzung für den Fall, dass der Name eines Felds und die Variable, mit der das Feld ausgefüllt wird, identisch sind. Refaktorieren Sie die Methode build
von DocumentScreen
wie folgt.
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',
),
),
],
),
);
}
}
Die Syntax des Variablenmusters :modified
ist eine Kurzschreibweise für modified: modified
. Wenn Sie eine neue lokale Variable mit einem anderen Namen benötigen, können Sie stattdessen modified: localModified
schreiben.
- Hot Refresh, um dasselbe Ergebnis wie im vorherigen Schritt zu sehen. Das Verhalten ist identisch: haben Sie Ihren Code präziser gestaltet.
7. Daten mithilfe von Mustern extrahieren
In bestimmten Kontexten stimmen Muster nicht nur überein und destrukturieren sie. Sie können auch eine Entscheidung darüber treffen, was der Code tut, je nachdem, ob das Muster übereinstimmt oder nicht. Diese werden als reflektierte Muster bezeichnet.
Das im letzten Schritt verwendete Muster zur Variablendeklaration ist ein unwiderrufliches Muster: Der Wert muss mit dem Muster übereinstimmen. Andernfalls ist es ein Fehler und es findet keine Desstrukturierung statt. Denken Sie an eine beliebige Deklaration oder Zuweisung von Variablen. Sie können einer Variablen keinen Wert zuweisen, wenn sie nicht vom gleichen Typ sind.
Refugierbare Muster hingegen werden in Kontexten des Ablaufs der Steuerung verwendet:
- Sie erwarten, dass einige Werte, mit denen sie verglichen werden, nicht übereinstimmen.
- Sie sollen den Kontrollfluss beeinflussen, je nachdem, ob der Wert übereinstimmt oder nicht.
- Sie unterbrechen die Ausführung nicht mit einem Fehler, wenn sie nicht übereinstimmen. Sie fahren einfach mit der nächsten Anweisung fort.
- Sie können Variablen destrukturieren und binden, die nur verwendet werden können, wenn sie übereinstimmen.
JSON-Werte ohne Muster lesen
In diesem Abschnitt lesen Sie Daten ohne Musterabgleich, um zu sehen, wie Muster Ihnen bei der Arbeit mit JSON-Daten helfen können.
- Ersetzen Sie die vorherige Version von
metadata
durch eine, die Werte aus der_json
-Zuordnung liest. Kopieren Sie diese Version vonmetadata
und fügen Sie sie in die KlasseDocument
ein:
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.
}
}
Mit diesem Code wird überprüft, ob die Daten korrekt strukturiert sind, ohne Muster zu verwenden. In einem späteren Schritt verwenden Sie den Musterabgleich, um die gleiche Validierung mit weniger Code durchzuführen. Vor weiteren Aktionen werden drei Prüfungen durchgeführt:
- Die JSON-Datei enthält die erwartete Datenstruktur:
if (_json.containsKey('metadata'))
- Die Daten haben den erwarteten Typ:
if (metadataJson is Map)
- Ob die Daten nicht null sind, was in der vorherigen Prüfung implizit bestätigt wurde.
JSON-Werte mithilfe eines Zuordnungsmusters lesen
Mit einem widerlegbaren Muster können Sie mithilfe eines Zuordnungsmusters überprüfen, ob die JSON-Datei die erwartete Struktur hat.
- Ersetzen Sie die vorherige Version von
metadata
durch diesen Code:
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.
}
}
Hier sehen Sie eine neue Art von if-Anweisung, die in Dart 3 eingeführt wurde: die if-case. Der Anfragetext wird nur ausgeführt, wenn das Fallmuster mit den Daten in _json
übereinstimmt. Dieser Abgleich führt zu denselben Prüfungen, die Sie in der ersten Version von metadata
geschrieben haben, um den eingehenden JSON-Code zu validieren. Mit diesem Code wird Folgendes validiert:
_json
ist ein Kartentyp._json
enthält einenmetadata
-Schlüssel._json
ist nicht null._json['metadata']
ist auch ein Kartentyp._json['metadata']
enthält die Schlüsseltitle
undmodified
.title
undlocalModified
sind Strings und nicht null.
Stimmt der Wert nicht überein, wird das Muster widerlegt, d. h. es weigert sich, die Ausführung fortzusetzen, und mit der else
-Klausel fortfahren. Wenn der Abgleich erfolgreich ist, destrukturiert das Muster die Werte von title
und modified
aus der Zuordnung und bindet sie an neue lokale Variablen.
Eine vollständige Liste der Muster finden Sie in der Tabelle im Abschnitt „Muster“ der Funktionsspezifikation.
8. App für weitere Muster vorbereiten
Bisher haben Sie den metadata
-Teil der JSON-Daten bearbeitet. In diesem Schritt optimieren Sie die Geschäftslogik ein wenig weiter, damit die Daten in der blocks
-Liste verarbeitet und in Ihrer App gerendert werden können.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Klasse zum Speichern von Daten erstellen
- Fügen Sie
data.dart
die neue KlasseBlock
hinzu, mit der die Daten für einen der Blöcke in den JSON-Daten gelesen und gespeichert werden.
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');
}
}
}
Der Factory-Konstruktor fromJson()
verwendet das gleiche if-Case mit einem Kartenmuster, das Sie bereits kennen.
Beachten Sie, dass json
mit dem Kartenmuster übereinstimmt, auch wenn einer der Schlüssel, checked
, im Muster nicht berücksichtigt wird. In Kartenmustern werden alle Einträge im Map-Objekt ignoriert, die nicht explizit im Muster berücksichtigt werden.
Rückgabe einer Liste von Block-Objekten
- Fügen Sie als Nächstes der Klasse
Document
die neue FunktiongetBlocks()
hinzu.getBlocks()
parst die JSON in Instanzen derBlock
-Klasse und gibt eine Liste von Blöcken zurück, die in Ihrer UI gerendert werden sollen:
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.
}
Die Funktion getBlocks()
gibt eine Liste von Block
-Objekten zurück, die Sie später zum Erstellen der UI verwenden. Eine bekannte if-case-Anweisung führt eine Validierung durch und wandelt den Wert der blocks
-Metadaten in eine neue List
namens blocksJson
um. Ohne Muster wird die Methode toList()
zum Umwandeln benötigt.
Das Listenliteral enthält eine Sammlung für, um die neue Liste mit Block
-Objekten zu füllen.
In diesem Abschnitt werden keine musterbezogenen Funktionen vorgestellt, die Sie noch nicht in diesem Codelab ausprobiert haben. Im nächsten Schritt bereiten Sie das Rendern der Listenelemente in Ihrer Benutzeroberfläche vor.
9. Muster zum Anzeigen des Dokuments verwenden
Sie haben nun Ihre JSON-Daten mithilfe einer if-case-Anweisung und abweisbaren Mustern erfolgreich destrukturiert und neu zusammengesetzt. Die „If-Case“-Regel ist jedoch nur eine der Verbesserungen bei der Steuerung von Ablaufstrukturen, die mit Mustern einhergehen. Jetzt wenden Sie Ihr Wissen über nachprüfbare Muster auf Wechselaussagen an.
Mit Mustern mit Switch-Anweisungen steuern, was gerendert wird
- Erstellen Sie in
main.dart
das neue WidgetBlockWidget
, das den Stil jedes Blocks anhand des zugehörigentype
-Felds festlegt.
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,
),
);
}
}
Mit der Switch-Anweisung in der Methode build
wird das Feld type
des Objekts block
aktiviert.
- Bei der ersten Case-Anweisung wird ein konstantes Stringmuster verwendet. Das Muster stimmt überein, wenn
block.type
gleich dem konstanten Werth1
ist. - Die zweite Case-Anweisung verwendet ein Logik-oder-Muster mit zwei konstanten Stringmustern als Submuster. Das Muster stimmt überein, wenn
block.type
mit einem der Submusterp
odercheckbox
übereinstimmt.
- Der letzte Fall ist das Platzhaltermuster
_
. Platzhalter in Switch-Cases stimmen mit allem anderen überein. Sie verhalten sich genauso wiedefault
-Klauseln, die in Switch-Anweisungen weiterhin zulässig sind (sie sind nur etwas ausführlicher).
Platzhaltermuster können überall dort verwendet werden, wo ein Muster zulässig ist, z. B. in einem Variablendeklarationsmuster: var (title, _) = document.metadata;
In diesem Kontext bindet der Platzhalter keine Variable. Das zweite Feld wird verworfen.
Im nächsten Abschnitt erfahren Sie mehr über Schalterfunktionen, nachdem Sie die Block
-Objekte angezeigt haben.
Dokumentinhalt anzeigen
Erstellen Sie eine lokale Variable, die die Liste der Block
-Objekte enthält, indem Sie getBlocks()
in der build
-Methode des DocumentScreen
-Widgets aufrufen.
- Ersetzen Sie die vorhandene Methode
build
inDocumentationScreen
durch diese Version:
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.
],
),
);
}
}
Die Zeile BlockWidget(block: blocks[index])
erstellt ein BlockWidget
-Widget für jedes Element in der Liste der Blöcke, die von der Methode getBlocks()
zurückgegeben werden.
- Führen Sie die Anwendung aus. Die Blöcke sollten auf dem Bildschirm angezeigt werden:
10. Switch-Ausdrücke verwenden
Muster bieten switch
und case
viele Funktionen. Damit Sie sie an noch mehr Stellen verwenden können, verfügt Dart über Switch-Ausdrücke. Bei einer Reihe von Fällen kann ein Wert direkt für eine Variablenzuweisung oder eine Rückgabeanweisung bereitgestellt werden.
Switch-Anweisung in einen Switch-Ausdruck konvertieren
Der Dart-Analysator unterstützt Sie bei der Durchführung von Änderungen an Ihrem Code.
- Bewegen Sie den Cursor auf die Switch-Anweisung aus dem vorherigen Abschnitt.
- Klicken Sie auf die Glühbirne, um die verfügbaren Unterstützungen aufzurufen.
- Wählen Sie die Unterstützung für In Ausdruck konvertieren aus.
Die neue Version dieses Codes sieht so aus:
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,
),
);
}
}
Ein Switch-Ausdruck sieht ähnlich wie eine Switch-Anweisung aus, entfernt jedoch das Schlüsselwort case
und verwendet =>
, um das Muster vom Text der Groß- und Kleinschreibung zu trennen. Im Gegensatz zu Switch-Anweisungen geben Switch-Ausdrücke einen Wert zurück und können überall dort verwendet werden, wo ein Ausdruck verwendet werden kann.
11. Objektmuster verwenden
Dart ist eine objektorientierte Sprache. Daher gelten Muster für alle Objekte. In diesem Schritt aktivieren Sie ein Objektmuster und destrukturieren Objekteigenschaften, um die Logik für das Datums-Rendering in Ihrer Benutzeroberfläche zu verbessern.
Eigenschaften aus Objektmustern extrahieren
In diesem Abschnitt verbessern Sie mithilfe von Mustern, wie das Datum der letzten Änderung angezeigt wird.
- Fügen Sie die Methode
formatDate
zumain.dart
hinzu:
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',
};
}
Diese Methode gibt einen Switch-Ausdruck zurück, der den Wert difference
, ein Duration
-Objekt, aktiviert. Er stellt die Zeitspanne zwischen today
und dem modified
-Wert aus den JSON-Daten dar.
Jeder Fall des Switch-Ausdrucks verwendet ein Objektmuster, das durch Aufrufen von Getter für die Eigenschaften des Objekts inDays
und isNegative
übereinstimmt. Die Syntax sieht so aus, als würde sie ein Duration-Objekt erstellen, greift aber tatsächlich auf Felder im difference
-Objekt zu.
In den ersten drei Fällen werden konstante Submuster 0
, 1
und -1
verwendet, um die Objekteigenschaft inDays
abzugleichen und den entsprechenden String zurückzugeben.
In den letzten beiden Fällen wird die Dauer nach heute, gestern und morgen behandelt:
- Wenn die Eigenschaft
isNegative
mit dem Muster der booleschen Konstantentrue
übereinstimmt, was bedeutet, dass das Änderungsdatum in der Vergangenheit liegt, wird vor Tagen angezeigt. - Wird der Unterschied in diesem Fall nicht erkannt, muss die Dauer eine positive Anzahl von Tagen sein (keine explizite Bestätigung mit
isNegative: false
erforderlich). Das Änderungsdatum liegt also in der Zukunft und zeigt Tage ab heute an.
Formatierungslogik für Wochen hinzufügen
- Fügen Sie Ihrer Formatierungsfunktion zwei neue Fälle hinzu, um Zeiträume zu identifizieren, die länger als 7 Tage sind, damit sie in der Benutzeroberfläche als Wochen angezeigt werden können:
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',
};
}
Mit diesem Code werden Guard-Klauseln eingeführt:
- In einer Guard-Klausel wird das Schlüsselwort
when
nach einem Muster in Groß- und Kleinschreibung verwendet. - Sie können in if-Cases, Switch-Anweisungen und Switch-Ausdrücken verwendet werden.
- Sie fügen einem Muster erst eine Bedingung hinzu, nachdem es abgeglichen wurde.
- Wird die Guard-Klausel als „false“ ausgewertet, wird das gesamte Muster widerruft und die Ausführung mit dem nächsten Fall fortgesetzt.
Das neu formatierte Datum zur Benutzeroberfläche hinzufügen
- Aktualisieren Sie schließlich die Methode
build
inDocumentScreen
, um die FunktionformatDate
zu verwenden:
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]);
},
),
),
],
),
);
}
}
- Hot Refresh, um die Änderungen in Ihrer App zu sehen:
12. Sichere dir die Chance auf einen umfassenden Wechsel
Beachten Sie, dass Sie am Ende des letzten Switches weder einen Platzhalter noch eine Standardfallschreibweise verwendet haben. Obwohl es sich empfiehlt, bei Werten, die möglicherweise nicht erfasst werden, immer Groß- und Kleinschreibung zu verwenden, ist dies in einem einfachen Beispiel wie diesem in Ordnung, da Sie wissen, dass die von Ihnen definierten Fälle alle möglichen Werte berücksichtigen, die inDays
annehmen können.
Wenn jeder Fall bei einem Wechsel behandelt wird, spricht man von einem umfassenden Wechsel. Beispielsweise ist die Aktivierung eines bool
-Typs vollständig, wenn es Fälle für true
und false
gibt. Das Aktivieren eines enum
-Typs ist umfassend, wenn auch für jeden Enum-Wert Fälle vorhanden sind, da Enums eine feste Anzahl konstanter Werte darstellen.
Dart 3 hat mit dem neuen Klassenmodifikator sealed
die Erfüllungsprüfung auf Objekte und Klassenhierarchien erweitert. Refaktorieren Sie Ihre Block
-Klasse als versiegelte übergeordnete Klasse.
Abgeleitete Klassen erstellen
- Erstellen Sie in
data.dart
drei neue Klassen (HeaderBlock
,ParagraphBlock
undCheckboxBlock
), dieBlock
erweitern:
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);
}
Jede dieser Klassen entspricht den verschiedenen type
-Werten aus der ursprünglichen JSON-Datei: 'h1'
, 'p'
und 'checkbox'
.
Versiegen Sie die Basisklasse
- Markieren Sie die
Block
-Klasse alssealed
. Refaktorieren Sie dann den if-case als Switch-Ausdruck, der die abgeleitete Klasse zurückgibt, die dem in der JSON-Datei angegebenentype
entspricht:
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'),
};
}
}
Das sealed
-Keyword ist ein Klassenmodifikator. Das bedeutet, dass du diese Klasse nur in derselben Bibliothek erweitern oder implementieren kannst. Da das Analyse-Tool die Untertypen dieser Klasse kennt, meldet es einen Fehler, wenn ein Switch einen davon nicht abdeckt und nicht vollständig ist.
Verwenden Sie einen Switch-Ausdruck, um Widgets anzuzeigen
- Aktualisieren Sie die BlockWidget-Klasse in
main.dart
mit einem Switch-Ausdruck, der für jeden Fall Objektmuster verwendet:
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),
],
),
},
);
}
}
In Ihrer ersten Version von BlockWidget
haben Sie ein Feld eines Block
-Objekts aktiviert, um eine TextStyle
zurückzugeben. Jetzt wechseln Sie eine Instanz des Block
-Objekts und gleichen sie mit den Objektmustern ab, die die abgeleiteten Klassen darstellen. Dabei werden die Eigenschaften des Objekts extrahiert.
Der Dart-Analysator kann prüfen, ob jede Unterklasse im Switch-Ausdruck verarbeitet wird, da Sie Block
zu einer versiegelten Klasse gemacht haben.
Beachten Sie auch, dass Sie hier mit einem Switch-Ausdruck das Ergebnis direkt an das child
-Element übergeben können, im Gegensatz zur separaten Rückgabeanweisung, die zuvor benötigt wurde.
- Hot Refresh, um zum ersten Mal gerenderte JSON-Daten des Kästchens zu sehen:
13. Glückwunsch
Sie haben erfolgreich mit Mustern, Datensätzen, erweiterten Switch- und Case-Funktionen sowie mit versiegelten Klassen experimentiert. Sie haben viele Informationen behandelt, aber diese Elemente nur knapp an der Oberfläche gekratzt. Weitere Informationen zu Mustern finden Sie in der Funktionsspezifikation.
Die verschiedenen Mustertypen, die unterschiedlichen Kontexte, in denen sie auftreten können, und die potenzielle Verschachtelung von Submustern lassen die Möglichkeiten des Verhaltens nahezu endlos. Aber sie sind leicht zu erkennen.
Sie können sich verschiedene Möglichkeiten vorstellen, wie Inhalte in Flutter mithilfe von Mustern dargestellt werden können. Mithilfe von Mustern können Sie Daten sicher extrahieren und mit wenigen Codezeilen Ihre UI erstellen.
Was liegt als Nächstes an?
- Sehen Sie sich die Dokumentation zu Mustern, Datensätzen, erweiterten Switches und Fällen sowie zu Klassenmodifikatoren im Abschnitt "Sprache" der Dart-Dokumentation an.
Referenzdokumente
Den vollständigen Beispielcode finden Sie Schritt für Schritt im flutter/codelabs
-Repository.
Detaillierte Spezifikationen für jede neue Funktion finden Sie in den ursprünglichen Designdokumenten: