О практической работе
1. Введение
Dart 3 вводит шаблоны в язык, новую крупную категорию грамматики. Помимо этого нового способа написания кода Dart, есть несколько других языковых улучшений, включая
- записи для объединения данных разных типов,
- модификаторы класса для управления доступом и
- новые выражения switch и операторы if-case .
Эти функции расширяют ваши возможности при написании кода Dart. В этой кодовой лаборатории вы узнаете, как использовать их, чтобы сделать ваш код более компактным, оптимизированным и гибким.
Эта кодовая лаборатория предполагает, что вы немного знакомы с Flutter и Dart. Если вы чувствуете, что немного заржавели, рассмотрите возможность освежить основы с помощью следующих ресурсов:
Что вы построите
Эта кодовая лаборатория создает приложение, которое отображает документ JSON во Flutter. Приложение имитирует JSON, поступающий из внешнего источника. JSON содержит данные документа, такие как дата изменения, название, заголовки и абзацы. Вы пишете код, чтобы аккуратно упаковать данные в записи, чтобы их можно было передавать и распаковывать везде, где это необходимо вашим виджетам Flutter.
Затем вы используете шаблоны для построения соответствующего виджета, когда значение соответствует этому шаблону. Вы также видите, как использовать шаблоны для деструктуризации данных в локальные переменные.
Чему вы научитесь
- Как создать запись, которая хранит несколько значений разных типов.
- Как вернуть несколько значений из функции с помощью записи.
- Как использовать шаблоны для сопоставления, проверки и деструктуризации данных из записей и других объектов.
- Как привязать сопоставленные с шаблоном значения к новым или существующим переменным.
- Как использовать новые возможности оператора switch, выражения switch и операторы if-case.
- Как воспользоваться проверкой полноты , чтобы гарантировать обработку каждого случая в операторе switch или выражении switch.
2. Настройте свою среду
- Установите Flutter SDK .
- Установите редактор , например Visual Studio Code (VS Code).
- Выполните шаги по настройке платформы хотя бы для одной целевой платформы (iOS, Android, настольный компьютер или веб-браузер).
3. Создать проект
Прежде чем погрузиться в шаблоны, записи и другие новые функции, уделите время созданию проекта Flutter, для которого вы напишете весь свой код.
Создать проект Flutter
- Используйте команду
flutter create
для создания нового проекта с именемpatterns_codelab
. Флаг--empty
предотвращает создание стандартного приложения счетчика в файлеlib/main.dart
, который вам в любом случае придется удалить.
flutter create --empty patterns_codelab
- Затем откройте каталог
patterns_codelab
с помощью VS Code.
code patterns_codelab
Установите минимальную версию SDK
- Установите ограничение версии SDK для вашего проекта, чтобы он зависел от Dart 3 или выше.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Настройте проект
На этом этапе вы создаете или обновляете два файла Dart:
- Файл
main.dart
, содержащий виджеты для приложения, и - Файл
data.dart
, предоставляющий данные приложения.
Вы продолжите изменять оба этих файла на последующих этапах.
Определите данные для приложения
- Создайте новый файл
lib/data.dart
и добавьте в него следующий код:
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"
}
]
}
''';
Представьте себе программу, получающую данные из внешнего источника, например, потока ввода-вывода или HTTP-запроса. В этой кодовой лаборатории вы упрощаете этот более реалистичный вариант использования, имитируя входящие данные JSON с помощью многострочной строки в переменной documentJson
.
Данные JSON определяются в классе Document
. Позже в этой кодовой лаборатории вы добавите функции, которые возвращают данные из проанализированного JSON. Этот класс определяет и инициализирует поле _json
в своем конструкторе.
Запустите приложение
Команда flutter create
создает файл lib/main.dart
как часть структуры файлов Flutter по умолчанию.
- Чтобы создать отправную точку для приложения, замените содержимое
main.dart
следующим кодом:
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'))]),
);
}
}
Вы добавили в приложение следующие два виджета:
-
DocumentApp
устанавливает последнюю версию Material Design для тематизации пользовательского интерфейса. -
DocumentScreen
обеспечивает визуальную компоновку страницы с помощью виджетаScaffold
.
- Чтобы убедиться, что все работает гладко, запустите приложение на хост-компьютере, нажав «Запустить и отладить» :
- По умолчанию Flutter выбирает любую доступную целевую платформу. Чтобы изменить целевую платформу, выберите текущую платформу в строке состояния:
Вы должны увидеть пустую рамку с элементами title
и body
, определенными в виджете DocumentScreen
:
5. Создание и возврат записей
На этом этапе вы используете записи для возврата нескольких значений из вызова функции. Затем вы вызываете эту функцию в виджете DocumentScreen
для доступа к значениям и их отображения в пользовательском интерфейсе.
Создать и вернуть запись
- В
data.dart
добавьте новый метод получения в класс Document с именемmetadata
, который возвращает запись:
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.
}
Возвращаемым типом этой функции является запись с двумя полями, одно из которых имеет тип String
, а другое — тип DateTime
.
Оператор return создает новую запись, заключая два значения в скобки: (title, modified: now)
.
Первое поле является позиционным и не имеет имени, а второе поле имеет имя modified
.
Доступ к полям записи
- В виджете
DocumentScreen
вызовите метод полученияmetadata
в методеbuild
, чтобы получить запись и получить доступ к ее значениям:
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}')),
],
),
);
}
}
Метод получения metadata
возвращает запись, которая назначается локальной переменной metadataRecord
. Записи — это легкий и простой способ вернуть несколько значений из одного вызова функции и назначить их переменной.
Чтобы получить доступ к отдельным полям, содержащимся в этой записи, можно использовать встроенный синтаксис геттера записи.
- Чтобы получить позиционное поле (поле без имени, например
title
), используйте метод getter
в записи. Это возвращает только неименованные поля. - Именованные поля, такие как
modified
не имеют позиционного геттера, поэтому вы можете использовать его имя напрямую, напримерmetadataRecord.modified
.
Чтобы определить имя геттера для позиционного поля, начните с $1
и пропустите именованные поля. Например:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Горячая перезагрузка, чтобы увидеть значения JSON, отображаемые в приложении. Плагин VS Code Dart выполняет горячую перезагрузку каждый раз, когда вы сохраняете файл.
Вы можете видеть, что каждое поле фактически сохранило свой тип.
- Метод
Text()
принимает строку в качестве первого аргумента. -
modified
поле представляет собой DateTime и преобразуется вString
с помощью интерполяции строк .
Другой типобезопасный способ возврата различных типов данных — определение класса, что более многословно.
6. Сопоставьте и разрушьте с помощью шаблонов
Записи могут эффективно собирать различные типы данных и легко передавать их. Теперь улучшите свой код с помощью шаблонов .
Шаблон представляет собой структуру, которую может принимать одно или несколько значений, как чертеж. Шаблоны сравниваются с фактическими значениями, чтобы определить, совпадают ли они.
Некоторые шаблоны, когда они совпадают, деструктурируют сопоставленное значение, извлекая из него данные. Деструктуризация позволяет вам распаковывать значения из объекта, чтобы назначить их локальным переменным или выполнить дальнейшее сопоставление с ними.
Разбить запись на локальные переменные
- Реорганизуйте метод
build
DocumentScreen
для вызоваmetadata
и использования их для инициализации объявления переменной шаблона :
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.
}
}
Шаблон записи (title, modified: modified)
содержит два шаблона переменных , которые соответствуют полям записи, возвращаемым metadata
.
- Выражение соответствует подшаблону, поскольку результатом является запись с двумя полями, одно из которых называется
modified
. - Поскольку они совпадают, шаблон объявления переменной деструктурирует выражение, обращаясь к его значениям и привязывая их к новым локальным переменным тех же типов и имен,
String title
иDateTime modified
.
Есть сокращение для случая, когда имя поля и заполняющая его переменная совпадают. Реорганизуйте метод build
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'))]),
);
}
}
Синтаксис шаблона переменной :modified
является сокращением для modified: modified
. Если вам нужна новая локальная переменная с другим именем, вы можете написать modified: localModified
вместо этого.
- Горячая перезагрузка, чтобы увидеть тот же результат, что и в предыдущем шаге. Поведение точно такое же; вы просто сделали свой код более лаконичным.
7. Используйте шаблоны для извлечения данных
В определенных контекстах шаблоны не только сопоставляются и деструктурируются, но также могут принимать решение о том, что делает код , на основе того, соответствует шаблон или нет. Они называются опровергаемыми шаблонами .
Шаблон объявления переменной, который вы использовали на последнем шаге, является неопровержимым шаблоном : значение должно соответствовать шаблону, иначе это ошибка, и деструктуризация не произойдет. Подумайте о любом объявлении или назначении переменной; вы не можете присвоить значение переменной, если они не одного типа.
С другой стороны, опровержимые шаблоны используются в контекстах потока управления:
- Они ожидают , что некоторые значения, с которыми они сравнивают, не совпадут.
- Они предназначены для влияния на поток управления в зависимости от того, совпадает ли значение.
- Они не прерывают выполнение из-за ошибки, если они не совпадают, а просто переходят к следующему оператору.
- Они могут деструктурировать и связывать переменные, которые можно использовать только при их соответствии
Чтение значений JSON без шаблонов
В этом разделе вы прочитаете данные без сопоставления с шаблоном, чтобы увидеть, как шаблоны могут помочь вам работать с данными JSON.
- Замените предыдущую версию
metadata
на ту, которая считывает значения из карты_json
. Скопируйте и вставьте эту версиюmetadata
в классDocument
:
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.
}
}
Этот код проверяет, что данные структурированы правильно, без использования шаблонов. На более позднем этапе вы используете сопоставление с шаблоном, чтобы выполнить ту же проверку, используя меньше кода. Он выполняет три проверки, прежде чем делать что-либо еще:
- JSON содержит ожидаемую структуру данных:
if (_json.containsKey('metadata'))
- Данные имеют ожидаемый тип :
if (metadataJson is Map)
- Данные не равны нулю , что неявно подтверждено в предыдущей проверке.
Чтение значений JSON с использованием шаблона карты
С помощью опровержимого шаблона можно проверить, что JSON имеет ожидаемую структуру, используя шаблон карты .
- Замените предыдущую версию
metadata
этим кодом:
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.
}
}
Здесь вы видите новый тип оператора if (введенный в Dart 3), if-case . Тело case выполняется только в том случае, если шаблон case соответствует данным в _json
. Это соответствие выполняет те же проверки, которые вы написали в первой версии metadata
для проверки входящего JSON. Этот код проверяет следующее:
-
_json
— это тип карты. -
_json
содержит ключmetadata
. -
_json
не является пустым. -
_json['metadata']
также является типом карты. -
_json['metadata']
содержитtitle
ключей иmodified
. -
title
иlocalModified
являются строками и не равны NULL.
Если значение не совпадает, шаблон опровергает (отказывается продолжать выполнение) и переходит к пункту else
. Если совпадение успешно, шаблон деструктурирует значения title
и modified
из карты и привязывает их к новым локальным переменным.
Полный список шаблонов см. в таблице в разделе «Шаблоны» спецификации функций.
8. Подготовьте приложение к большему количеству шаблонов
До сих пор вы занимались metadata
JSON-данных. На этом этапе вы немного усовершенствуете свою бизнес-логику, чтобы обрабатывать данные в списке blocks
и отображать их в вашем приложении.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Создайте класс, который хранит данные
- Добавьте новый класс
Block
вdata.dart
, который используется для чтения и хранения данных для одного из блоков в данных 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');
}
}
}
Конструктор фабрики fromJson()
использует тот же if-case с шаблоном карты, который вы видели ранее.
Вы увидите, что данные JSON выглядят как ожидаемый шаблон, хотя в них есть дополнительная часть информации, называемая checked
, которой нет в шаблоне. Это потому, что когда вы используете такие шаблоны (называемые «шаблоны карт»), они заботятся только о конкретных вещах, которые вы определили в шаблоне, и игнорируют все остальное в данных.
Возврат списка объектов Block
- Затем добавьте новую функцию
getBlocks()
в классDocument
.getBlocks()
анализирует JSON на экземпляры классаBlock
и возвращает список блоков для отображения в пользовательском интерфейсе:
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.
}
Функция getBlocks()
возвращает список объектов Block
, которые вы используете позже для построения пользовательского интерфейса. Знакомый оператор if-case выполняет проверку и преобразует значение метаданных blocks
в новый List
с именем blocksJson
(без шаблонов вам понадобится метод toList()
для преобразования).
Литерал списка содержит коллекцию для заполнения нового списка объектами Block
.
В этом разделе не представлены никакие функции, связанные с шаблонами, которые вы еще не пробовали в этой кодовой лаборатории. На следующем этапе вы готовитесь к отображению элементов списка в вашем пользовательском интерфейсе.
9. Используйте шаблоны для отображения документа
Теперь вы успешно деструктурируете и перекомпонуете свои данные JSON, используя оператор if-case и опровергаемые шаблоны. Но if-case — это только одно из усовершенствований для управления структурами потока, которые идут с шаблонами. Теперь вы применяете свои знания об опровергаемых шаблонах для переключения операторов.
Управляйте тем, что отображается, используя шаблоны с операторами switch
- В
main.dart
создайте новый виджетBlockWidget
, который определяет стиль каждого блока на основе его поляtype
.
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),
);
}
}
Оператор switch в методе build
переключает поле type
объекта block
.
- В первом операторе case используется константный шаблон строки . Шаблон совпадает, если
block.type
равен константному значениюh1
. - Второй оператор case использует шаблон логического ИЛИ с двумя шаблонами константных строк в качестве своих подшаблонов. Шаблон совпадает, если
block.type
совпадает с одним из подшаблоновp
илиcheckbox
.
- Последний случай — шаблон подстановочных знаков ,
_
. Подстановочные знаки в случаях switch соответствуют всему остальному. Они ведут себя так же, как и предложенияdefault
, которые по-прежнему разрешены в операторах switch (они просто немного более многословны).
Подстановочные шаблоны можно использовать везде, где разрешен шаблон, например, в шаблоне объявления переменной: var (title, _) = document.metadata;
В этом контексте подстановочный знак не связывает никакую переменную. Он отбрасывает второе поле.
В следующем разделе вы узнаете о дополнительных функциях переключателя после отображения объектов Block
.
Отобразить содержимое документа
Создайте локальную переменную, содержащую список объектов Block
, вызвав getBlocks()
в методе build
виджета DocumentScreen
.
- Замените существующий метод
build
вDocumentationScreen
на эту версию:
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.
],
),
);
}
}
Строка BlockWidget(block: blocks[index])
создает виджет BlockWidget
для каждого элемента в списке блоков, возвращаемом методом getBlocks()
.
- Запустите приложение, и вы увидите на экране блоки:
10. Используйте выражения-переключатели
Шаблоны добавляют много возможностей к switch
и case
. Чтобы сделать их пригодными для использования в большем количестве мест, в Dart есть switch выражения . Серия case может предоставить значение непосредственно для присваивания переменной или оператора return.
Преобразовать оператор switch в выражение switch
Анализатор Dart поможет вам внести изменения в код.
- Переместите курсор на оператор switch из предыдущего раздела.
- Нажмите на лампочку, чтобы просмотреть доступные ассистенты.
- Выберите помощник по выражению «Преобразовать для переключения» .
Новая версия этого кода выглядит так:
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),
);
}
}
Выражение switch похоже на оператор switch, но оно исключает ключевое слово case
и использует =>
для отделения шаблона от тела case. В отличие от операторов switch, выражения switch возвращают значение и могут использоваться везде, где можно использовать выражение.
11. Используйте шаблоны объектов
Dart — объектно-ориентированный язык, поэтому шаблоны применяются ко всем объектам. На этом этапе вы включаете шаблон объекта и деструктурируете свойства объекта, чтобы улучшить логику рендеринга даты вашего пользовательского интерфейса.
Извлечение свойств из шаблонов объектов
В этом разделе вы улучшите способ отображения даты последнего изменения с помощью шаблонов.
- Добавьте метод
formatDate
вmain.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',
};
}
Этот метод возвращает выражение switch, которое включает difference
значений, объект Duration
. Он представляет собой промежуток времени между today
и modified
значением из данных JSON.
Каждый случай выражения switch использует шаблон объекта, который сопоставляется путем вызова геттеров для свойств объекта inDays
и isNegative
. Синтаксис выглядит так, как будто он создает объект Duration, но на самом деле он обращается к полям в объекте difference
.
В первых трех случаях используются константные подшаблоны 0
, 1
и -1
для сопоставления свойства объекта inDays
и возврата соответствующей строки.
Последние два случая охватывают периоды времени, выходящие за рамки сегодняшнего, вчерашнего и завтрашнего дня:
- Если свойство
isNegative
соответствует шаблону логической константыtrue
, что означает, что дата изменения была в прошлом, отображается значение days ago . - Если в этом случае разница не улавливается, то продолжительность должна быть положительным числом дней (нет необходимости явно проверять с помощью
isNegative: false
), поэтому дата изменения находится в будущем и отображает количество дней с текущего момента .
Добавить логику форматирования для недель
- Добавьте два новых случая в функцию форматирования, чтобы определить периоды длительностью более 7 дней, чтобы пользовательский интерфейс мог отображать их как недели :
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',
};
}
В этом кодексе вводятся защитные положения :
- В защитном предложении после шаблона case используется ключевое слово
when
. - Их можно использовать в конструкциях if, операторах switch и выражениях switch.
- Они добавляют условие к шаблону только после его сопоставления .
- Если условие защиты оценивается как ложное, весь шаблон опровергается , и выполнение переходит к следующему случаю.
Добавьте новую отформатированную дату в пользовательский интерфейс
- Наконец, обновите метод
build
вDocumentScreen
, чтобы использовать функциюformatDate
:
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]);
},
),
),
],
),
);
}
}
- Горячая перезагрузка, чтобы увидеть изменения в вашем приложении:
12. Запечатайте класс для исчерпывающего переключения
Обратите внимание, что вы не использовали подстановочный знак или случай по умолчанию в конце последнего переключения. Хотя хорошей практикой является всегда включать случай для значений, которые могут не сработать, это нормально в простом примере, как этот, поскольку вы знаете, что определенные вами случаи учитывают все возможные значения, которые может потенциально принять inDays
.
Когда обрабатывается каждый случай в switch, это называется исчерпывающим switch . Например, переключение на тип bool
является исчерпывающим, когда у него есть случаи для true
и false
. Переключение на тип enum
также является исчерпывающим, когда есть случаи для каждого из значений enum, поскольку enum представляют фиксированное количество константных значений .
Dart 3 расширил проверку полноты для объектов и иерархий классов с новым модификатором класса sealed
. Реорганизуйте класс Block
в sealed superclass.
Создайте подклассы
- В
data.dart
создайте три новых класса —HeaderBlock
,ParagraphBlock
иCheckboxBlock
— которые расширяют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);
}
Каждый из этих классов соответствует различным значениям type
из исходного JSON: 'h1'
, 'p'
и 'checkbox'
.
Запечатайте суперкласс
- Пометьте класс
Block
какsealed
. Затем выполните рефакторинг if-case как switch-выражения, которое возвращает подкласс, соответствующийtype
указанному в 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'),
};
}
}
Ключевое слово sealed
— это модификатор класса , который означает, что вы можете расширить или реализовать этот класс только в той же библиотеке . Поскольку анализатор знает подтипы этого класса, он сообщает об ошибке, если переключатель не охватывает один из них и не является исчерпывающим.
Используйте выражение switch для отображения виджетов
- Обновите класс
BlockWidget
вmain.dart
с помощью выражения switch, которое использует шаблоны объектов для каждого случая:
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),
],
),
},
);
}
}
В вашей первой версии BlockWidget
вы включили поле объекта Block
для возврата TextStyle
. Теперь вы включаете экземпляр самого объекта Block
и сопоставляете с шаблонами объектов , которые представляют его подклассы, извлекая свойства объекта в процессе.
Анализатор Dart может проверить, что каждый подкласс обрабатывается в выражении switch, поскольку вы сделали Block
запечатанным классом.
Также обратите внимание, что использование выражения switch здесь позволяет передавать результат непосредственно child
элементу, в отличие от отдельного оператора return, который требовался ранее.
- Горячая перезагрузка, чтобы увидеть JSON-данные флажка, отрисованные в первый раз:
13. Поздравления
Вы успешно экспериментировали с шаблонами, записями, улучшенным switch и case и запечатанными классами. Вы охватили много информации, но только едва коснулись поверхности этих функций. Для получения дополнительной информации о шаблонах см. спецификацию функций .
Различные типы паттернов, различные контексты, в которых они могут появляться, и потенциальное вложение подпаттернов делают возможности поведения, казалось бы, бесконечными . Но их легко увидеть.
Вы можете представить себе всевозможные способы отображения контента во Flutter с использованием шаблонов. Используя шаблоны, вы можете безопасно извлекать данные для построения своего пользовательского интерфейса в несколько строк кода.
Что дальше?
- Ознакомьтесь с документацией по шаблонам, записям, расширенным переключателям и случаям, а также модификаторам классов в разделе «Язык» документации Dart.
Справочные документы
Полный пример кода, шаг за шагом, смотрите в репозитории flutter/codelabs
.
Подробные спецификации каждой новой функции можно найти в исходной проектной документации: