1. Einführung
In Dart 3 werden Muster eingeführt, eine wichtige neue Kategorie der Grammatik. Neben dieser neuen Möglichkeit, Dart-Code zu schreiben, gibt es noch weitere Sprachverbesserungen, darunter:
- Datensätze zum Bündeln von Daten verschiedener Typen,
- Klassenmodifikatoren zur Zugriffssteuerung und
- neue Switch-Ausdrücke und Wenn-Bedingungen.
Mit diesen Funktionen haben Sie beim Schreiben von Dart-Code mehr Möglichkeiten. In diesem Codelab erfahren Sie, wie Sie sie verwenden können, um Ihren Code kompakter, schlanker und flexibler zu gestalten.
In diesem Codelab wird davon ausgegangen, dass Sie mit Flutter und Dart vertraut sind. Wenn Sie das Gefühl haben, dass Sie etwas eingerostet sind, können Sie sich mit den folgenden Ressourcen auf die Grundlagen zurückbesinnen:
Aufgaben
In diesem Codelab wird eine Anwendung erstellt, die ein JSON-Dokument in Flutter anzeigt. Die Anwendung simuliert JSON-Daten, die aus einer externen Quelle stammen. Die JSON-Datei enthält Dokumentdaten wie das Änderungsdatum, den Titel, Überschriften und Absätze. Sie schreiben Code, um Daten übersichtlich in Datensätze zu verpacken, damit sie an die Stelle übertragen und dort entpackt werden können, wo sie für Ihre Flutter-Widgets benötigt werden.
Wenn der Wert mit dem Muster übereinstimmt, können Sie dann das entsprechende Widget erstellen. Außerdem erfahren Sie, wie Sie mithilfe von Mustern Daten in lokale Variablen destrukturieren.
Aufgaben in diesem Lab
- So erstellen Sie einen Datensatz, in dem mehrere Werte mit unterschiedlichen Typen gespeichert werden.
- So geben Sie mithilfe eines Datensatzes mehrere Werte aus einer Funktion zurück.
- Informationen zum Abgleichen, Validieren und Destrukturieren von Daten aus Einträgen und anderen Objekten mithilfe von Mustern
- So binden Sie Werte mit Musterübereinstimmung an neue oder vorhandene Variablen.
- Neue Funktionen für Switch-Anweisungen, Switch-Ausdrücke und If-Case-Anweisungen
- Wie Sie die Vollständigkeitsprüfung nutzen, um sicherzustellen, dass in einer Switch-Anweisung oder einem Switch-Ausdruck alle Fälle abgedeckt sind.
2. Umgebung einrichten
- Installieren Sie das Flutter SDK.
- Richten Sie einen Editor ein, z. B. Visual Studio Code (VS Code).
- Führen Sie die Schritte zur Plattformeinrichtung für mindestens eine Zielplattform (iOS, Android, Computer oder Webbrowser) aus.
3. Projekt erstellen
Bevor wir uns mit Mustern, Einträgen und anderen neuen Funktionen befassen, erstellen Sie ein einfaches Flutter-Projekt, für das Sie den gesamten Code schreiben.
Flutter-Projekt erstellen
- Erstellen Sie mit dem Befehl
flutter create
ein neues Projekt mit dem Namenpatterns_codelab
. Das Flag--empty
verhindert das Erstellen der Standard-Zähler-App in derlib/main.dart
-Datei, die Sie ohnehin entfernen müssten.
flutter create --empty patterns_codelab
- Öffnen Sie dann das Verzeichnis
patterns_codelab
mit VS Code.
code patterns_codelab
Mindest-SDK-Version festlegen
- Legen Sie die SDK-Versionseinschränkung für Ihr Projekt so fest, dass sie von Dart 3 oder höher abhängt.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Projekt einrichten
In diesem Schritt erstellen oder aktualisieren Sie zwei Dart-Dateien:
- Die Datei
main.dart
mit Widgets für die App und - Die
data.dart
-Datei, die die Daten der App enthält.
Sie werden diese beiden Dateien in den folgenden Schritten weiter ändern.
Daten für die App definieren
- Erstellen Sie eine neue Datei namens
lib/data.dart
und fügen Sie ihr 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"
}
]
}
''';
Angenommen, ein Programm empfängt Daten von einer externen Quelle, z. B. von einem I/O-Stream oder einer HTTP-Anfrage. In diesem Codelab vereinfachen Sie diesen realistischeren Anwendungsfall, indem Sie eingehende JSON-Daten mit einem mehrzeiligen String in der Variablen documentJson
simulieren.
Die JSON-Daten sind in der Klasse Document
definiert. Später in diesem Codelab fügen Sie Funktionen hinzu, die Daten aus dem geparsten JSON zurückgeben. Diese Klasse definiert und initialisiert das Feld _json
in ihrem 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 Ausgangspunkt 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 beiden Widgets hinzugefügt:
- Mit
DocumentApp
wird die neueste Version von Material Design für die Benutzeroberfläche eingerichtet. DocumentScreen
stellt das visuelle Layout der Seite mit demScaffold
-Widget bereit.
- Um sicherzustellen, dass alles reibungslos funktioniert, führen Sie die App auf Ihrem Hostcomputer aus, indem Sie auf Ausführen und debuggen klicken:
- Standardmäßig wird die verfügbare Zielplattform von Flutter ausgewählt. Wenn Sie die Zielplattform ändern möchten, wählen Sie in der Statusleiste die aktuelle Plattform aus:
Sie sollten einen leeren Frame mit den im DocumentScreen
-Widget definierten title
- und body
-Elementen sehen:
5. Einträge 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
der Dokumentklasse eine neue Getter-Methode namensmetadata
hinzu, die einen Datensatz 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 vom Typ String
und das andere vom Typ DateTime
.
Die Rückgabeanweisung erstellt einen neuen Datensatz, indem die beiden Werte in Klammern gesetzt werden, (title, modified: now)
.
Das erste Feld ist positionsbasiert und ohne Namen und das zweite Feld heißt modified
.
Felder des Zugriffseintrags
- Rufen Sie im
DocumentScreen
-Widget diemetadata
-Gettermethode in derbuild
-Methode auf, damit Sie den Datensatz 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 metadata
-Gettermethode gibt einen Datensatz zurück, der der lokalen Variablen metadataRecord
zugewiesen wird. Mit Einträgen können Sie ganz einfach mehrere Werte aus einem einzelnen Funktionsaufruf zurückgeben und einer Variablen zuweisen.
Um auf die einzelnen Felder in diesem Datensatz zuzugreifen, können Sie die integrierte Getter-Syntax von Datensätzen verwenden.
- Wenn Sie ein Positionsfeld (ein Feld ohne Namen, z. B.
title
) abrufen möchten, verwenden Sie den Getter$<num>
für den Datensatz. Es werden nur unbenannte Felder zurückgegeben. - Für benannte Felder wie
modified
gibt es keinen Positional-Getter. Sie können den Namen also direkt verwenden, z. B.metadataRecord.modified
.
Um den Namen eines Getters für ein Positionsfeld zu ermitteln, beginnen Sie bei $1
und überspringen Sie benannte Felder. Beispiel:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Führen Sie einen Hot-Reload durch, damit die JSON-Werte in der App angezeigt werden. Das VS Code Dart-Plug-in führt jedes Mal einen Hot-Reload durch, wenn Sie eine Datei speichern.
Sie sehen, dass der Typ jedes Felds beibehalten wurde.
- Die Methode
Text()
nimmt einen String als erstes Argument an. - Das Feld
modified
ist ein DateTime-Feld und wird mithilfe der Stringinterpolation in einString
umgewandelt.
Die andere typsichere Möglichkeit, verschiedene Datentypen zurückzugeben, besteht darin, eine Klasse zu definieren. Das ist jedoch aufwendiger.
6. Mit Mustern abgleichen und destrukturieren
Mit Einträgen können verschiedene Datentypen effizient erfasst und einfach weitergegeben werden. Verbessern Sie nun Ihren Code mithilfe von Mustern.
Ein Muster stellt eine Struktur dar, die einen oder mehrere Werte annehmen kann, ähnlich wie ein Bauplan. Muster werden mit tatsächlichen Werten verglichen, um festzustellen, ob sie übereinstimmen.
Bei einigen Mustern wird der übereinstimmende Wert destrukturiert, indem Daten daraus extrahiert werden. Mit der Destrukturierung können Sie Werte aus einem Objekt entpacken, um sie lokalen Variablen zuzuweisen oder weitere Übereinstimmungen damit durchzuführen.
Datensatz in lokale Variablen destrukturieren
- Refaktorisieren Sie die
build
-Methode vonDocumentScreen
, ummetadata
aufzurufen und damit eine Deklaration einer Mustervariablen zu initialisieren:
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 Datensatzmuster (title, modified: modified)
enthält zwei variable Muster, die mit den Feldern des von metadata
zurückgegebenen Datensatzes abgeglichen werden.
- Der Ausdruck stimmt mit dem Untermuster überein, da das Ergebnis ein Datensatz mit zwei Feldern ist, von denen eines den Namen
modified
hat. - Da sie übereinstimmen, wird der Ausdruck durch das Variablendeklarationsmuster destrukturiert, indem auf seine Werte zugegriffen und sie neuen lokalen Variablen mit denselben Typen und Namen,
String title
undDateTime modified
, zugewiesen werden.
Es gibt eine Kurzschreibweise, wenn der Name eines Felds mit der Variablen übereinstimmt, die es füllt. Erstellen Sie eine neue Version der build
-Methode von DocumentScreen
.
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 Abkürzung für modified: modified
. Wenn Sie eine neue lokale Variable mit einem anderen Namen benötigen, können Sie stattdessen modified: localModified
eingeben.
- Führen Sie einen Hot-Reload durch, um dasselbe Ergebnis wie im vorherigen Schritt zu sehen. Das Verhalten ist genau das gleiche. Sie haben Ihren Code nur prägnanter gestaltet.
7. Muster zum Extrahieren von Daten verwenden
In bestimmten Kontexten werden Muster nicht nur abgeglichen und destrukturiert, sondern es kann auch eine Entscheidung darüber getroffen werden, was der Code tut, je nachdem, ob das Muster übereinstimmt oder nicht. Diese werden als widerlegbare Muster bezeichnet.
Das Muster für die Variablendeklaration, das Sie im letzten Schritt verwendet haben, ist ein unwiderlegbares Muster: Der Wert muss mit dem Muster übereinstimmen, andernfalls tritt ein Fehler auf und die Destrukturierung wird nicht durchgeführt. Denken Sie an eine Variablendeklaration oder -zuweisung: Sie können einer Variablen keinen Wert zuweisen, wenn sie nicht vom selben Typ ist.
Widerlegbare Muster werden dagegen in Kontrollflusskontexten verwendet:
- Er erwartet, dass einige der Werte, mit denen er vergleicht, nicht übereinstimmen.
- Sie sollen den Programmablauf beeinflussen, je nachdem, ob der Wert übereinstimmt oder nicht.
- Sie unterbrechen die Ausführung nicht mit einem Fehler, wenn sie nicht übereinstimmen, sondern fahren einfach mit der nächsten Anweisung fort.
- Sie können Variablen destrukturieren und binden, die nur verwendbar sind, 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 Zuordnung_json
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.
}
}
Dieser Code prüft, ob die Daten ohne Verwendung von Mustern korrekt strukturiert sind. In einem späteren Schritt verwenden Sie Musterabgleich, um dieselbe Validierung mit weniger Code auszuführen. Bevor etwas anderes passiert, 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)
- Die Daten sind nicht null, was in der vorherigen Prüfung implizit bestätigt wurde.
JSON-Werte mit einem Zuordnungsmuster lesen
Mit einem widerlegbaren Muster können Sie mithilfe eines Zuordnungsmusters prü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-Bedingung (in Dart 3 eingeführt), den If-Block. Der Fallkörper wird nur ausgeführt, wenn das Fallmuster mit den Daten in _json
übereinstimmt. Bei dieser Übereinstimmung werden dieselben Prüfungen durchgeführt, die Sie in der ersten Version von metadata
zur Validierung der eingehenden JSON-Datei geschrieben haben. Dieser Code validiert Folgendes:
_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.
Wenn der Wert nicht übereinstimmt, wird das Muster widerlegt (die Ausführung wird nicht fortgesetzt) und mit der else
-Klausel fortgefahren. Wenn eine Übereinstimmung gefunden wird, werden die Werte von title
und modified
aus der Zuordnung destrukturiert und an neue lokale Variablen gebunden.
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 Teil metadata
der JSON-Daten angesprochen. In diesem Schritt optimieren Sie Ihre Geschäftslogik noch etwas, um die Daten in der Liste blocks
zu verarbeiten und in Ihrer App zu rendern.
{
"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 Konstruktor fromJson()
verwendet denselben If-Block mit einem Zuordnungsmuster, das Sie bereits kennen.
Sie sehen, dass die JSON-Daten dem erwarteten Muster entsprechen, obwohl sie eine zusätzliche Information namens checked
enthalten, die nicht im Muster enthalten ist. Das liegt daran, dass bei diesen Mustern (sogenannte „Kartenmuster“) nur die im Muster definierten Elemente berücksichtigt werden und alle anderen Daten ignoriert werden.
Liste der Blockobjekte zurückgeben
- Fügen Sie der Klasse
Document
als Nächstes die neue FunktiongetBlocks()
hinzu.getBlocks()
parst das JSON in Instanzen der KlasseBlock
und gibt eine Liste der Blöcke zurück, die in der Benutzeroberfläche 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 Benutzeroberfläche verwenden. Eine bekannte if-Bedingung führt eine Validierung durch und wandelt den Wert der blocks
-Metadaten in eine neue List
mit dem Namen blocksJson
um. Ohne Muster müssten Sie die Methode toList()
zum Umwandeln verwenden.
Das Listenliteral enthält eine collection for, um die neue Liste mit Block
-Objekten zu füllen.
In diesem Abschnitt werden keine musterbezogenen Funktionen vorgestellt, die Sie in diesem Codelab noch nicht ausprobiert haben. Im nächsten Schritt bereiten Sie das Rendern der Listenelemente in Ihrer Benutzeroberfläche vor.
9. Muster für die Darstellung des Dokuments verwenden
Sie können Ihre JSON-Daten jetzt mithilfe einer Wenn-Bedingung und widerlegbaren Mustern erfolgreich destrukturieren und neu zusammensetzen. Aber der If-Bedingungsblock ist nur eine der Verbesserungen bei Kontrollflussstrukturen, die mit Mustern einhergehen. Jetzt wenden Sie Ihr Wissen über widerlegbare Muster auf Switch-Anweisungen an.
Mit Switch-Anweisungen festlegen, was mithilfe von Mustern gerendert wird
- Erstellen Sie in
main.dart
ein neues Widget,BlockWidget
, das das Styling jedes Blocks basierend auf seinem Feldtype
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,
),
);
}
}
Die Switch-Anweisung in der build
-Methode aktiviert das Feld type
des block
-Objekts.
- Die erste Fallanweisung verwendet ein konstantes Stringmuster. Das Muster stimmt überein, wenn
block.type
dem konstanten Werth1
entspricht. - In der zweiten Fallanweisung wird ein logisches OR-Muster mit zwei konstanten Stringmustern als Untermuster verwendet. Das Muster stimmt überein, wenn
block.type
mit einem der Untermusterp
odercheckbox
übereinstimmt.
- Der letzte Fall ist ein Platzhaltermuster,
_
. Platzhalter in Switch-Cases stimmen mit allen 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 Muster für die Variablendeklaration: var (title, _) = document.metadata;
In diesem Kontext wird der Platzhalter nicht an eine Variable gebunden. Das zweite Feld wird verworfen.
Im nächsten Abschnitt erfahren Sie mehr über die Funktionen von Schaltern, 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
build
-Methode 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.
],
),
);
}
}
In Zeile BlockWidget(block: blocks[index])
wird für jedes Element in der Liste der Blöcke, die von der Methode getBlocks()
zurückgegeben werden, ein BlockWidget
-Widget erstellt.
- Führen Sie die Anwendung aus. Die Blöcke sollten dann auf dem Bildschirm angezeigt werden:
10. Switch-Ausdrücke verwenden
Mithilfe von Mustern können Sie switch
und case
noch effektiver einsetzen. Damit sie an mehr Stellen verwendet werden können, gibt es in Dart Switch-Ausdrücke. Eine Reihe von Fällen kann einem Variablenzuweisungs- oder Rückgabewert direkt einen Wert zuweisen.
Switch-Anweisung in einen Switch-Ausdruck umwandeln
Der Dart-Analysator bietet Hilfe, um Änderungen an Ihrem Code vorzunehmen.
- Bewegen Sie den Cursor zur Switch-Anweisung aus dem vorherigen Abschnitt.
- Klicken Sie auf die Glühbirne, um die verfügbaren Vorbereitungen aufzurufen.
- Wählen Sie die Option In Switch-Ausdruck umwandeln 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 ähnelt einer Switch-Anweisung, ohne das Schlüsselwort case
. Stattdessen wird =>
verwendet, um das Muster vom Fallkörper 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 der Datumsdarstellung Ihrer Benutzeroberfläche zu verbessern.
Eigenschaften aus Objektmustern extrahieren
In diesem Abschnitt verbessern Sie die Darstellung des Datums der letzten Änderung mithilfe von Mustern.
- Fügen Sie
main.dart
die MethodeformatDate
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 entspricht dem Zeitraum zwischen today
und dem modified
-Wert aus den JSON-Daten.
Für jeden Fall des Switch-Ausdrucks wird ein Objektmuster verwendet, das über den Aufruf von Gettern für die Eigenschaften inDays
und isNegative
des Objekts übereinstimmt. Die Syntax sieht so aus, als würde ein Duration-Objekt erstellt, tatsächlich wird aber auf Felder des difference
-Objekts zugegriffen.
In den ersten drei Fällen werden die konstanten Untermuster 0
, 1
und -1
verwendet, um mit der Objekteigenschaft inDays
zu übereinstimmen und den entsprechenden String zurückzugeben.
Die letzten beiden Fälle beziehen sich auf Zeiträume, die über heute, gestern und morgen hinausgehen:
- Wenn das Attribut
isNegative
dem Muster für boolesche Konstantentrue
entspricht, d. h. das Änderungsdatum in der Vergangenheit liegt, wird vor Tagen angezeigt. - Wenn dieser Fall nicht zutrifft, muss die Dauer eine positive Anzahl von Tagen sein (es ist nicht erforderlich, dies explizit mit
isNegative: false
zu überprüfen). Das Änderungsdatum liegt dann in der Zukunft und wird als Tage in der Zukunft angezeigt.
Formatierungslogik für Wochen hinzufügen
- Fügen Sie Ihrer Formatierungsfunktion zwei neue Fälle hinzu, um Zeiträume von mehr als 7 Tagen zu identifizieren, damit sie in der Benutzeroberfläche als Wochen angezeigt werden:
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',
};
}
In diesem Code werden Guard-Klauseln eingeführt:
- In einer Guard-Klausel wird nach einem Fallmuster das Schlüsselwort
when
verwendet. - Sie können in If-Bedingungen, Switch-Anweisungen und Switch-Ausdrücken verwendet werden.
- Sie fügen einem Muster erst dann eine Bedingung hinzu, wenn eine Übereinstimmung gefunden wurde.
- Wenn die Guard-Klausel den Wert „false“ ergibt, wird das gesamte Muster widerlegt und die Ausführung geht mit dem nächsten Fall weiter.
Das neu formatierte Datum in die Benutzeroberfläche einfügen
- Aktualisieren Sie abschließend 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]);
},
),
),
],
),
);
}
}
- Führen Sie einen Hot-Reload durch, um die Änderungen in Ihrer App zu sehen:
12. Eine Klasse für eine vollständige Umschaltung versiegeln
Beachten Sie, dass Sie am Ende des letzten Schalters keinen Platzhalter oder Standardfall verwendet haben. Es ist zwar empfehlenswert, immer einen Fall für Werte anzugeben, die möglicherweise nicht berücksichtigt werden, in einem einfachen Beispiel wie diesem ist das aber in Ordnung, da Sie wissen, dass die von Ihnen definierten Fälle alle möglichen Werte für inDays
abdecken.
Wenn alle Fälle in einer Switch-Anweisung verarbeitet werden, wird sie als ausschließende Switch-Anweisung bezeichnet. Wenn Sie beispielsweise einen bool
-Typ aktivieren, ist die Abdeckung vollständig, wenn er Fälle für true
und false
enthält. Wenn es auch Fälle für jeden der Werte des Enumerationstyps gibt, ist die Aktivierung eines enum
-Typs erschöpfend, da Enumerationen eine feste Anzahl von konstanten Werten darstellen.
In Dart 3 wurde die Vollständigkeitsprüfung mit dem neuen Klassenmodifikator sealed
auf Objekte und Klassenhierarchien ausgeweitet. Erstellen Sie eine neue Block
-Klasse als versiegelte Superklasse.
Unterklassen 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 dem ursprünglichen JSON-Objekt: 'h1'
, 'p'
und 'checkbox'
.
Superklasse versiegeln
- Markieren Sie die
Block
-Klasse alssealed
. Refaktorisieren Sie dann den If-Case in einen Switch-Ausdruck, der die Unterklasse zurückgibt, die der in JSON 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 Keyword sealed
ist ein Klassenmodifizierer. Das bedeutet, dass Sie diese Klasse nur in derselben Bibliothek erweitern oder implementieren können. Da der Analyser die Untertypen dieser Klasse kennt, meldet er einen Fehler, wenn ein Switch einen davon nicht abdeckt und nicht vollständig ist.
Switch-Ausdruck zum Anzeigen von Widgets verwenden
- Aktualisieren Sie die
BlockWidget
-Klasse inmain.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 ein TextStyle
zurückzugeben. Jetzt wechseln Sie zu einer Instanz des Block
-Objekts und vergleichen es mit Objektmustern, die seine Unterklassen 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 mit einem Switch-Ausdruck das Ergebnis direkt an das child
-Element übergeben können, im Gegensatz zur separaten Rückgabeanweisung, die zuvor erforderlich war.
- Führen Sie einen Hot-Reload durch, damit die JSON-Daten für das Kästchen zum ersten Mal gerendert werden:
13. Glückwunsch
Sie haben erfolgreich mit Mustern, Protokollen, erweiterten Switch- und Case-Anweisungen sowie versiegelten Klassen experimentiert. Sie haben viele Informationen vermittelt, aber diese Funktionen nur ansatzweise behandelt. Weitere Informationen zu Mustern finden Sie in der Feature-Spezifikation.
Die verschiedenen Mustertypen, die verschiedenen Kontexte, in denen sie auftreten können, und die mögliche Verschachtelung von Untermustern machen die Möglichkeiten im Verhalten scheinbar endlos. Sie sind aber leicht zu erkennen.
Es gibt viele Möglichkeiten, wie Sie Inhalte in Flutter mithilfe von Mustern anzeigen können. Mithilfe von Mustern können Sie Daten sicher extrahieren, um Ihre Benutzeroberfläche mit wenigen Codezeilen zu erstellen.
Was liegt als Nächstes an?
- Weitere Informationen zu Mustern, Protokollen, erweiterten Switch- und Case-Anweisungen sowie zu Klassenmodifikatoren finden Sie in der Dart-Dokumentation im Abschnitt zur Sprache.
Referenzdokumente
Den vollständigen Beispielcode findest du im flutter/codelabs
-Repository.
Ausführliche Spezifikationen für jede neue Funktion finden Sie in den ursprünglichen Designdokumenten: