1. Wprowadzenie
W Dart 3 wprowadziliśmy do języka wzorce, czyli nową kategorię gramatyki. Oprócz tego nowego sposobu pisania kodu w Dart wprowadziliśmy kilka innych ulepszeń języka, w tym:
- rekordy do łączenia danych różnych typów,
- modyfikatory klas do kontrolowania dostępu,
- nowe wyrażenia switch i instrukcje if-case.
Te funkcje zwiększają możliwości podczas pisania kodu w Dart. Z tego ćwiczenia w Codelabs dowiesz się, jak ich używać, aby Twój kod był bardziej zwarty, uproszczony i elastyczny.
W tym laboratorium zakładamy, że masz już pewną wiedzę na temat Fluttera i Darta. Jeśli czujesz, że musisz sobie przypomnieć podstawy, skorzystaj z tych materiałów:
Co utworzysz
W tym ćwiczeniu utworzysz aplikację, która wyświetla dokument JSON w Flutterze. Aplikacja symuluje dane JSON pochodzące ze źródła zewnętrznego. Plik JSON zawiera dane dokumentu, takie jak data modyfikacji, tytuł, nagłówki i akapit. Piszesz kod, aby starannie pakować dane w rekordy, dzięki czemu można je przesyłać i rozpakowywać wszędzie tam, gdzie są potrzebne widżety Fluttera.
Następnie używasz wzorców do tworzenia odpowiedniego widżetu, gdy wartość pasuje do danego wzorca. Dowiesz się też, jak używać wzorców do rozkładania danych na zmienne lokalne.

Czego się nauczysz
- Jak utworzyć rekord, który przechowuje wiele wartości o różnych typach.
- Jak zwrócić wiele wartości z funkcji za pomocą rekordu.
- Jak używać wzorców do dopasowywania, weryfikowania i dekonstrukcji danych z rekordów i innych obiektów.
- Jak powiązać wartości dopasowane do wzorca z nowymi lub dotychczasowymi zmiennymi.
- Jak korzystać z nowych możliwości instrukcji switch, wyrażeń switch i instrukcji if-case.
- Jak korzystać ze sprawdzania wyczerpania, aby mieć pewność, że każdy przypadek jest obsługiwany w instrukcji switch lub wyrażeniu switch.
2. Konfigurowanie środowiska
- Zainstaluj pakiet SDK Flutter.
- Skonfiguruj edytor, np. Visual Studio Code (VS Code).
- Wykonaj czynności opisane w sekcji Konfiguracja platformy w przypadku co najmniej 1 platformy docelowej (iOS, Android, komputer lub przeglądarka).
3. Tworzenie projektu
Zanim zaczniesz korzystać z wzorców, rekordów i innych nowych funkcji, poświęć chwilę na utworzenie projektu Flutter, w którym będziesz pisać cały kod.
Tworzenie projektu Fluttera
- Aby utworzyć nowy projekt o nazwie
patterns_codelab, użyj poleceniaflutter create. Flaga--emptyzapobiega utworzeniu standardowej aplikacji licznika w plikulib/main.dart, którą i tak musiałbyś usunąć.
flutter create --empty patterns_codelab
- Następnie otwórz katalog
patterns_codelabw VS Code.
code patterns_codelab

Ustawianie minimalnej wersji pakietu SDK
- Ustaw ograniczenie wersji pakietu SDK dla projektu, aby zależał od Dart 3 lub nowszego.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Konfigurowanie projektu
W tym kroku utworzysz lub zaktualizujesz 2 pliki Dart:
main.dartplik zawierający widżety aplikacji,- Plik
data.dart, który zawiera dane aplikacji.
W kolejnych krokach będziesz nadal modyfikować oba te pliki.
Określanie danych aplikacji
- Utwórz nowy plik
lib/data.darti dodaj do niego ten kod:
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"
}
]
}
''';
Wyobraź sobie program, który otrzymuje dane ze źródła zewnętrznego, np. strumienia wejścia/wyjścia lub żądania HTTP. W tym laboratorium upraszczamy bardziej realistyczny przypadek użycia, symulując przychodzące dane JSON za pomocą wielowierszowego ciągu znaków w zmiennej documentJson.
Dane JSON są zdefiniowane w klasie Document. W dalszej części tego laboratorium dodasz funkcje, które zwracają dane z przeanalizowanego pliku JSON. Ta klasa definiuje i inicjuje pole _json w konstruktorze.
Uruchamianie aplikacji
Polecenie flutter create tworzy plik lib/main.dart w ramach domyślnej struktury plików Fluttera.
- Aby utworzyć punkt początkowy aplikacji, zastąp zawartość pliku
main.darttym kodem:
lib/main.dart
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(const DocumentApp());
}
class DocumentApp extends StatelessWidget {
const DocumentApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: DocumentScreen(document: Document()),
);
}
}
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({required this.document, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Title goes here')),
body: const Column(children: [Center(child: Text('Body goes here'))]),
);
}
}
Do aplikacji dodano te 2 widżety:
DocumentAppkonfiguruje najnowszą wersję Material Design do tworzenia motywów interfejsu.DocumentScreenudostępnia wizualny układ strony za pomocą widżetuScaffold.
- Aby upewnić się, że wszystko działa prawidłowo, uruchom aplikację na komputerze hosta, klikając Uruchom i debuguj:

- Domyślnie Flutter wybiera dostępną platformę docelową. Aby zmienić platformę docelową, wybierz bieżącą platformę na pasku stanu:

Powinna pojawić się pusta ramka z elementami title i body zdefiniowanymi w widżecie DocumentScreen:

5. Tworzenie i zwracanie rekordów
W tym kroku użyjesz rekordów, aby zwrócić wiele wartości z wywołania funkcji. Następnie wywołujesz tę funkcję w widżecie DocumentScreen, aby uzyskać dostęp do wartości i odzwierciedlić je w interfejsie.
Tworzenie i zwracanie rekordu
- W
data.dartdodaj do klasy Document nową metodę pobierającą o nazwiemetadata, która zwraca rekord:
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.
}
Typem zwracanym przez tę funkcję jest rekord z 2 polami: jedno jest typu String, a drugie – DateTime.
Instrukcja powrotu tworzy nowy rekord, umieszczając 2 wartości w nawiasach (title, modified: now).
Pierwsze pole jest pozycyjne i nie ma nazwy, a drugie ma nazwę modified.
Dostęp do pól rekordu
- W widżecie
DocumentScreenwywołaj metodę pobierającąmetadataw metodziebuild, aby uzyskać rekord i dostęp do jego wartości:
lib/main.dart
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({required this.document, super.key});
@override
Widget build(BuildContext context) {
final metadataRecord = document.metadata; // Add this line.
return Scaffold(
appBar: AppBar(title: Text(metadataRecord.$1)), // Modify this line,
body: Column(
children: [ // And the following line.
Center(child: Text('Last modified ${metadataRecord.modified}')),
],
),
);
}
}
Metoda pobierająca metadata zwraca rekord, który jest przypisywany do zmiennej lokalnej metadataRecord. Rekordy to prosty sposób na zwracanie wielu wartości z jednego wywołania funkcji i przypisywanie ich do zmiennej.
Aby uzyskać dostęp do poszczególnych pól w tym rekordzie, możesz użyć wbudowanej składni pobierania rekordów.
- Aby uzyskać pole pozycyjne (pole bez nazwy, np.
title), użyj funkcji pobierającejw rekordzie. Zwraca tylko pola bez nazwy. - Pola nazwane, takie jak
modified, nie mają funkcji pobierania pozycji, więc możesz użyć ich nazwy bezpośrednio, np.metadataRecord.modified.
Aby określić nazwę funkcji pobierającej dla pola pozycyjnego, zacznij od $1 i pomiń pola nazwane. Na przykład:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Włącz gorące przeładowanie, aby zobaczyć wartości JSON wyświetlane w aplikacji. Wtyczka VS Code Dart włącza gorące przeładowanie za każdym razem, gdy zapisujesz plik.

Jak widać, każde pole zachowało swój typ.
- Metoda
Text()przyjmuje jako pierwszy argument ciąg znaków. - Pole
modifiedjest typu DateTime i jest konwertowane naStringza pomocą interpolacji ciągów znaków.
Innym sposobem na zwracanie różnych typów danych w bezpieczny sposób jest zdefiniowanie klasy, co jest bardziej złożone.
6. Dopasowywanie i destrukturyzacja za pomocą wzorców
Rekordy mogą skutecznie zbierać różne typy danych i łatwo je przekazywać. Teraz ulepsz swój kod za pomocą wzorców.
Wzorzec to struktura, którą może przyjąć co najmniej 1 wartość, podobnie jak plan. Wzorce są porównywane z rzeczywistymi wartościami, aby określić, czy pasują do siebie.
Niektóre wzorce po dopasowaniu rozbijają dopasowaną wartość, wyodrębniając z niej dane. Destrukturyzacja umożliwia rozpakowywanie wartości z obiektu w celu przypisania ich do zmiennych lokalnych lub dalszego dopasowywania.
Rozkładanie rekordu na zmienne lokalne
- Przeprowadź refaktoryzację metody
buildklasyDocumentScreen, aby wywoływała metodęmetadatai używała jej do inicjowania deklaracji zmiennej wzorca:
lib/main.dart
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({required this.document, super.key});
@override
Widget build(BuildContext context) {
final (title, modified: modified) = document.metadata; // Modify
return Scaffold(
appBar: AppBar(title: Text(title)), // Modify from here...
body: Column(children: [Center(child: Text('Last modified $modified'))]),
); // To here.
}
}
Wzorzec rekordu (title, modified: modified) zawiera 2 wzorce zmiennych, które są dopasowywane do pól rekordu zwróconego przez metadata.
- Wyrażenie pasuje do wzorca, ponieważ wynikiem jest rekord z 2 polami, z których jedno ma nazwę
modified. - Ponieważ pasują do siebie, wzorzec deklaracji zmiennej rozkłada wyrażenie, uzyskując dostęp do jego wartości i przypisując je do nowych zmiennych lokalnych o tych samych typach i nazwach,
String titleiDateTime modified.
Istnieje skrót, który można zastosować, gdy nazwa pola i zmienna, która je wypełnia, są takie same. Refaktoryzuj metodę build klasy DocumentScreen w następujący sposób:
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'))]),
);
}
}
Składnia wzorca zmiennej :modified jest skrótem od modified: modified. Jeśli chcesz utworzyć nową zmienną lokalną o innej nazwie, możesz zamiast tego wpisać modified: localModified.
- Wykonaj gorące przeładowanie, aby zobaczyć ten sam wynik co w poprzednim kroku. Działanie jest dokładnie takie samo, tylko kod jest bardziej zwięzły.
7. Używanie wzorców do wyodrębniania danych
W niektórych kontekstach wzorce nie tylko dopasowują i destrukturyzują, ale mogą też podejmować decyzje o tym, co robi kod, w zależności od tego, czy wzorzec pasuje. Są to tzw. wzorce, które można obalić.
Wzorzec deklaracji zmiennej użyty w ostatnim kroku to wzorzec niepodważalny: wartość musi pasować do wzorca, w przeciwnym razie wystąpi błąd i destrukturyzacja nie nastąpi. Pomyśl o deklaracji lub przypisaniu zmiennej. Nie możesz przypisać wartości do zmiennej, jeśli nie są tego samego typu.
Wzorce, które można obalić, są z kolei używane w kontekstach przepływu sterowania:
- Oczekują, że niektóre wartości, z którymi porównują dane, nie będą się zgadzać.
- Mają one wpływać na przepływ sterowania w zależności od tego, czy wartość jest zgodna.
- Jeśli nie pasują, nie przerywają wykonywania z błędem, tylko przechodzą do następnej instrukcji.
- Mogą one rozkładać i wiązać zmienne, które są użyteczne tylko w przypadku dopasowania.
Odczytywanie wartości JSON bez wzorców
W tej sekcji odczytasz dane bez dopasowywania wzorców, aby zobaczyć, jak wzorce mogą pomóc w pracy z danymi JSON.
- Zastąp poprzednią wersję funkcji
metadatawersją, która odczytuje wartości z mapy_json. Skopiuj i wklej tę wersjęmetadatadoDocumentzajęć:
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.
}
}
Ten kod sprawdza, czy dane są prawidłowo uporządkowane, bez używania wzorców. W dalszej części tego przewodnika użyjesz dopasowywania wzorców, aby przeprowadzić tę samą weryfikację przy użyciu mniejszej ilości kodu. Zanim wykona jakiekolwiek inne działanie, przeprowadza 3 sprawdzenia:
- Plik JSON zawiera oczekiwaną strukturę danych:
if (_json.containsKey('metadata')) - Dane mają typ, którego oczekujesz:
if (metadataJson is Map) - że dane nie są puste, co zostało pośrednio potwierdzone w poprzednim sprawdzeniu;
Odczytywanie wartości JSON za pomocą wzorca mapy
W przypadku wzorca z możliwością odrzucenia możesz sprawdzić, czy plik JSON ma oczekiwaną strukturę, używając wzorca mapy.
- Zastąp poprzednią wersję pliku
metadatatym kodem:
lib/data.dart
class Document {
final Map<String, Object?> _json;
Document() : _json = jsonDecode(documentJson);
(String, {DateTime modified}) get metadata {
if (_json case { // Modify from here...
'metadata': {'title': String title, 'modified': String localModified},
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
} // to here.
}
}
Widzisz tu nowy rodzaj instrukcji warunkowej (wprowadzony w Dart 3), czyli if-case. Treść przypadku jest wykonywana tylko wtedy, gdy wzorzec przypadku pasuje do danych w _json. To dopasowanie wykonuje te same testy, które zostały napisane w pierwszej wersji metadata, aby sprawdzić przychodzący kod JSON. Ten kod sprawdza:
_jsonto typ mapy._jsonzawiera kluczmetadata._jsonnie ma wartości null._json['metadata']to także typ mapy._json['metadata']zawiera kluczetitleimodified.titleilocalModifiedto ciągi znaków, które nie mają wartości null.
Jeśli wartość nie pasuje, wzorzec odrzuca (przerywa wykonywanie) i przechodzi do klauzuli else. Jeśli dopasowanie się powiedzie, wzorzec rozpakuje wartości title i modified z mapy i powiąże je z nowymi zmiennymi lokalnymi.
Pełną listę wzorców znajdziesz w tabeli w sekcji Wzorce w specyfikacji funkcji.
8. Przygotowywanie aplikacji na więcej wzorców
Do tej pory zajmowaliśmy się częścią metadata danych JSON. W tym kroku dopracujesz logikę biznesową, aby obsługiwać dane na liście blocks i wyświetlać je w aplikacji.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Tworzenie klasy, która przechowuje dane
- Dodaj nową klasę
Blockdo klasydata.dart, która służy do odczytywania i przechowywania danych z jednego z bloków w danych 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');
}
}
}
Konstruktor fabryczny fromJson() używa tego samego wzorca if-case z mapą, który już znasz.
Zobaczysz, że dane JSON wyglądają zgodnie z oczekiwanym wzorcem, mimo że zawierają dodatkowy element o nazwie checked, którego nie ma we wzorcu. Dzieje się tak, ponieważ gdy używasz tego rodzaju wzorców (tzw. wzorców mapowania), uwzględniają one tylko określone elementy zdefiniowane we wzorcu i ignorują wszystko inne w danych.
Zwraca listę obiektów Block.
- Następnie dodaj nową funkcję
getBlocks()do klasyDocument.getBlocks()analizuje JSON w instancjach klasyBlocki zwraca listę bloków do renderowania w interfejsie:
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.
}
Funkcja getBlocks() zwraca listę obiektów Block, których używasz później do tworzenia interfejsu. Znana instrukcja if-case przeprowadza weryfikację i przekształca wartość metadanych blocks w nową zmienną List o nazwie blocksJson (bez wzorców do przekształcenia potrzebna byłaby metoda toList()).
Literał listy zawiera collection for, aby wypełnić nową listę obiektami Block.
W tej sekcji nie znajdziesz żadnych funkcji związanych ze wzorcami, których nie było w tym laboratorium. W następnym kroku przygotujesz renderowanie elementów listy w interfejsie.
9. Wyświetlanie dokumentu za pomocą wzorów
Dane JSON zostały rozdzielone i ponownie połączone przy użyciu instrukcji if-case i wzorców, które można odrzucić. Konstrukcja if-case to tylko jedno z ulepszeń struktur sterowania przepływem, które są dostępne w przypadku wzorców. Teraz wykorzystaj swoją wiedzę o wzorcach, które można obalić, w instrukcjach switch.
Kontrolowanie renderowania za pomocą wzorców z instrukcjami switch
- W
main.dartutwórz nowy widżetBlockWidget, który określa styl każdego bloku na podstawie polatype.
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),
);
}
}
Instrukcja switch w metodzie build przełącza pole type obiektu block.
- Pierwsza instrukcja case używa wzorca stałego ciągu znaków. Wzorzec pasuje, jeśli
block.typejest równa stałej wartościh1. - Druga instrukcja case używa wzoru logicznego OR z 2 wzorcami stałych ciągów znaków jako podwzorcami. Wzorzec pasuje, jeśli
block.typepasuje do dowolnego z podwzorcówplubcheckbox.
- Ostatni przypadek to wzorzec z symbolem wieloznacznym,
_. Symbole wieloznaczne w przypadkach instrukcji switch pasują do wszystkich innych elementów. Działają one tak samo jak klauzuledefault, które są nadal dozwolone w instrukcjach switch (są tylko nieco bardziej rozbudowane).
Wzorców z symbolami wieloznacznymi można używać wszędzie tam, gdzie dozwolony jest wzorzec, np. we wzorcu deklaracji zmiennej: var (title, _) = document.metadata;
W tym kontekście symbol wieloznaczny nie wiąże żadnej zmiennej. Odrzuca drugie pole.
W następnej sekcji dowiesz się więcej o funkcjach przełącznika po wyświetleniu obiektów Block.
Wyświetlanie zawartości dokumentu
Utwórz zmienną lokalną zawierającą listę obiektów Block, wywołując getBlocks() w metodzie build widżetu DocumentScreen.
- Zastąp istniejącą metodę
buildw plikuDocumentationScreentą wersją:
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.
],
),
);
}
}
Wiersz BlockWidget(block: blocks[index]) tworzy widżet BlockWidget dla każdego elementu na liście bloków zwróconej przez metodę getBlocks().
- Uruchom aplikację. Na ekranie powinny pojawić się bloki:

10. Używanie wyrażeń switch
Wzorce dodają wiele możliwości do switch i case. Aby można było ich używać w większej liczbie miejsc, w Dart wprowadzono wyrażenia switch. Seria przypadków może przekazywać wartość bezpośrednio do przypisania zmiennej lub instrukcji powrotu.
Przekształcanie instrukcji switch w wyrażenie switch
Analizator Dart udostępnia pomoc, która ułatwia wprowadzanie zmian w kodzie.
- Przesuń kursor do instrukcji switch z poprzedniej sekcji.
- Kliknij ikonę żarówki, aby wyświetlić dostępne wspomagania.
- Wybierz pomoc Przekonwertuj na wyrażenie switch.

Nowa wersja tego kodu wygląda tak:
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),
);
}
}
Wyrażenie switch wygląda podobnie do instrukcji switch, ale nie zawiera słowa kluczowego case i używa znaku => do oddzielenia wzorca od treści przypadku. W przeciwieństwie do instrukcji switch wyrażenia switch zwracają wartość i mogą być używane wszędzie tam, gdzie można używać wyrażeń.
11. Używanie wzorców obiektów
Dart to język obiektowy, więc wzorce mają zastosowanie do wszystkich obiektów. W tym kroku włączysz wzorzec obiektu i rozłożysz właściwości obiektu, aby ulepszyć logikę renderowania dat w interfejsie.
Wyodrębnianie właściwości z wzorców obiektów
W tej sekcji poprawisz sposób wyświetlania daty ostatniej modyfikacji za pomocą wzorców.
- Dodaj metodę
formatDatedomain.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',
};
}
Ta metoda zwraca wyrażenie przełącznika, które przełącza się na wartość difference, czyli obiekt Duration. Reprezentuje on przedział czasu między wartością today a wartością modified z danych JSON.
Każdy przypadek wyrażenia switch używa wzorca obiektu, który pasuje do wywoływania getterów we właściwościach obiektu inDays i isNegative. Składnia wygląda tak, jakby tworzyła obiekt Duration, ale w rzeczywistości uzyskuje dostęp do pól obiektu difference.
W pierwszych 3 przypadkach używane są stałe wzorce podrzędne 0, 1 i -1, aby dopasować właściwość obiektu inDays i zwrócić odpowiedni ciąg znaków.
Ostatnie 2 przypadki dotyczą okresów dłuższych niż dzisiaj, wczoraj i jutro:
- Jeśli właściwość
isNegativepasuje do wzoru stałej logicznejtrue, co oznacza, że data modyfikacji była w przeszłości, wyświetla się dni temu. - Jeśli ten przypadek nie obejmuje różnicy, czas trwania musi być dodatnią liczbą dni (nie trzeba tego sprawdzać za pomocą
isNegative: false), więc data modyfikacji jest w przyszłości i wyświetla się jako dni od teraz.
Dodawanie logiki formatowania tygodni
- Dodaj do funkcji formatowania 2 nowe przypadki, aby identyfikować okresy dłuższe niż 7 dni, tak aby interfejs mógł wyświetlać je jako tygodnie:
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',
};
}
Ten kod wprowadza klauzule warunkowe:
- Klauzula warunkowa używa słowa kluczowego
whenpo wzorcu przypadku. - Można ich używać w instrukcjach warunkowych if, instrukcjach switch i wyrażeniach switch.
- Warunek jest dodawany do wzorca dopiero po dopasowaniu.
- Jeśli klauzula warunkowa ma wartość false, cały wzorzec jest odrzucany, a wykonanie przechodzi do następnego przypadku.
Dodaj nowo sformatowaną datę do interfejsu
- Na koniec zaktualizuj metodę
buildw plikuDocumentScreen, aby używać funkcjiformatDate:
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]);
},
),
),
],
),
);
}
}
- Aby zobaczyć zmiany w aplikacji, użyj gorącego przeładowania:

12. Zablokuj klasę, aby umożliwić wyczerpujące przełączanie
Zwróć uwagę, że na końcu ostatniej instrukcji switch nie użyto symbolu wieloznacznego ani domyślnego przypadku. W tym prostym przykładzie nie musisz tego robić, ponieważ wiesz, że zdefiniowane przez Ciebie przypadki obejmują wszystkie możliwe wartości inDays.
Gdy wszystkie przypadki w instrukcji switch są obsługiwane, nazywa się ją wyczerpującą instrukcją switch. Na przykład włączenie typu bool jest wyczerpujące, gdy ma przypadki dla true i false. Włączenie typu enum jest wyczerpujące, gdy istnieją przypadki dla każdej wartości wyliczenia, ponieważ wyliczenia reprezentują stałą liczbę wartości stałych.
W Dart 3 rozszerzyliśmy sprawdzanie wyczerpania na obiekty i hierarchie klas za pomocą nowego modyfikatora klasy sealed. Zrefaktoryzuj klasę Block jako zapieczętowaną klasę nadrzędną.
Tworzenie podklas
- W języku
data.dartutwórz 3 nowe klasy –HeaderBlock,ParagraphBlockiCheckboxBlock– które rozszerzają klasęBlock:
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);
}
Każda z tych klas odpowiada różnym wartościom type z oryginalnego kodu JSON: 'h1', 'p' i 'checkbox'.
Zablokuj klasę nadrzędną
- Oznacz klasę
Blockjakosealed. Następnie przekształć instrukcję if-case w wyrażenie switch, które zwraca podklasę odpowiadającą wartościtypeokreślonej w pliku 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'),
};
}
}
Słowo kluczowe sealed jest modyfikatorem klasy, co oznacza, że możesz rozszerzyć lub zaimplementować tę klasę tylko w tej samej bibliotece. Analizator zna podtypy tej klasy, więc zgłasza błąd, jeśli instrukcja switch nie obejmuje jednego z nich i nie jest wyczerpująca.
Użyj wyrażenia switch, aby wyświetlać widżety
- Zaktualizuj klasę
BlockWidgetwmain.dartza pomocą wyrażenia switch, które w każdym przypadku używa wzorców obiektów:
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),
],
),
},
);
}
}
W pierwszej wersji BlockWidget włączono pole obiektu Block, aby zwracać wartość TextStyle. Teraz przełączasz instancję samego obiektu Block i dopasowujesz ją do wzorców obiektów reprezentujących jego podklasy, wyodrębniając przy tym właściwości obiektu.
Analizator Dart może sprawdzić, czy każda podklasa jest obsługiwana w wyrażeniu switch, ponieważ Block jest klasą zamkniętą.
Zwróć też uwagę, że użycie tutaj wyrażenia switch pozwala przekazać wynik bezpośrednio do elementu child, w przeciwieństwie do osobnej instrukcji powrotu, która była wcześniej potrzebna.
- Użyj gorącego przeładowania, aby po raz pierwszy zobaczyć wyrenderowane dane JSON pola wyboru:

13. Gratulacje
Udało Ci się przeprowadzić eksperymenty z wzorcami, rekordami, ulepszonymi instrukcjami switch i case oraz klasami zamkniętymi. Omówiliśmy wiele informacji, ale tylko powierzchownie. Więcej informacji o wzorcach znajdziesz w specyfikacji funkcji.
Różne typy wzorców, różne konteksty, w których mogą się pojawiać, oraz potencjalne zagnieżdżanie podwzorców sprawiają, że możliwości zachowań wydają się nieograniczone. Ale są dobrze widoczne.
Za pomocą wzorców możesz wyświetlać treści w Flutterze na różne sposoby. Korzystając z wzorców, możesz bezpiecznie wyodrębniać dane, aby utworzyć interfejs w kilku wierszach kodu.
Co dalej?
- Więcej informacji o wzorcach, rekordach, ulepszonych instrukcjach switch i case oraz modyfikatorach klas znajdziesz w sekcji Język w dokumentacji języka Dart.
Dokumentacja
Pełny przykładowy kod znajdziesz w flutter/codelabsrepozytorium.
Szczegółowe specyfikacje każdej nowej funkcji znajdziesz w oryginalnych dokumentach projektowych: