1. Giới thiệu
Dart 3 đưa ra các mẫu cho ngôn ngữ, một danh mục ngữ pháp mới quan trọng. Ngoài cách mới này để viết mã Dart, còn có một số cải tiến về ngôn ngữ khác, bao gồm
- bản ghi để nhóm dữ liệu thuộc nhiều loại,
- đối tượng sửa đổi lớp để kiểm soát quyền truy cập, và
- biểu thức chuyển đổi và câu lệnh if-case mới.
Những tính năng này mở rộng lựa chọn của bạn khi viết mã Dart. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng chúng để làm cho mã của mình nhỏ gọn, đơn giản và linh hoạt hơn.
Lớp học lập trình này giả định rằng bạn đã quen thuộc với Flutter và Dart. Nếu bạn cảm thấy tài nguyên đôi chút, hãy cân nhắc việc ôn lại những phần cơ bản bằng các tài nguyên sau:
Sản phẩm bạn sẽ tạo ra
Lớp học lập trình này sẽ tạo một ứng dụng hiển thị tài liệu JSON trong Flutter. Ứng dụng mô phỏng JSON đến từ một nguồn bên ngoài. Tệp JSON chứa dữ liệu tài liệu như ngày sửa đổi, tiêu đề, tiêu đề và đoạn văn bản. Bạn viết mã để đóng gói dữ liệu một cách gọn gàng vào các bản ghi sao cho dữ liệu có thể được chuyển và giải nén ở bất cứ nơi nào tiện ích Flutter của bạn cần đến.
Sau đó, bạn sẽ sử dụng các mẫu để tạo tiện ích phù hợp khi giá trị khớp với mẫu đó. Bạn cũng sẽ thấy cách sử dụng các mẫu để huỷ cấu trúc dữ liệu thành các biến cục bộ.
Kiến thức bạn sẽ học được
- Cách tạo bản ghi lưu trữ nhiều giá trị thuộc các loại khác nhau.
- Cách trả về nhiều giá trị từ một hàm bằng bản ghi.
- Cách sử dụng các mẫu để so khớp, xác thực và huỷ cấu trúc dữ liệu trong các bản ghi và đối tượng khác.
- Cách liên kết các giá trị đã so khớp mẫu với biến mới hoặc biến hiện có.
- Cách sử dụng các tính năng mới của câu lệnh chuyển đổi, biểu thức chuyển đổi và câu lệnh if-case.
- Cách tận dụng tính năng kiểm tra tính toàn diện để đảm bảo mọi trường hợp đều được xử lý trong câu lệnh chuyển đổi hoặc biểu thức chuyển đổi.
2. Thiết lập môi trường
- Cài đặt Flutter SDK.
- Thiết lập một trình chỉnh sửa, chẳng hạn như Visual Studio Code (VS Code).
- Thực hiện các bước Thiết lập nền tảng cho ít nhất một nền tảng mục tiêu (iOS, Android, Máy tính hoặc trình duyệt web).
3. Tạo dự án
Trước khi đi sâu vào các mẫu, bản ghi và các tính năng mới khác, hãy dành chút thời gian để tạo một dự án Flutter đơn giản để bạn viết toàn bộ mã của mình.
Tạo một dự án Flutter
- Dùng lệnh
flutter create
để tạo một dự án mới có tên làpatterns_codelab
. Cờ--empty
ngăn việc tạo ứng dụng bộ đếm chuẩn trong tệplib/main.dart
. Bạn vẫn phải xoá tệp này.
flutter create --empty patterns_codelab
- Sau đó, mở thư mục
patterns_codelab
bằng VS Code.
code patterns_codelab
Đặt phiên bản SDK tối thiểu
- Đặt quy tắc ràng buộc về phiên bản SDK để dự án của bạn phụ thuộc vào Dart 3 trở lên.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Thiết lập dự án
Ở bước này, bạn tạo hoặc cập nhật hai tệp Dart:
- Tệp
main.dart
chứa các tiện ích cho ứng dụng và - Tệp
data.dart
cung cấp dữ liệu của ứng dụng.
Bạn sẽ tiếp tục sửa đổi cả hai tệp này trong các bước tiếp theo.
Xác định dữ liệu cho ứng dụng
- Tạo một tệp mới,
lib/data.dart
và thêm mã sau vào tệp đó:
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"
}
]
}
''';
Hãy tưởng tượng một chương trình nhận dữ liệu từ nguồn bên ngoài, chẳng hạn như luồng I/O hoặc yêu cầu HTTP. Trong lớp học lập trình này, bạn sẽ đơn giản hoá trường hợp sử dụng thực tế hơn bằng cách mô phỏng dữ liệu JSON nhận được thông qua một chuỗi nhiều dòng trong biến documentJson
.
Dữ liệu JSON được xác định trong lớp Document
. Trong phần sau của lớp học lập trình này, bạn sẽ thêm các hàm trả về dữ liệu từ JSON đã phân tích cú pháp. Lớp này định nghĩa và khởi tạo trường _json
trong hàm khởi tạo.
Chạy ứng dụng
Lệnh flutter create
tạo tệp lib/main.dart
trong cấu trúc tệp Flutter mặc định.
- Để tạo điểm bắt đầu cho ứng dụng, hãy thay thế nội dung của
main.dart
bằng đoạn mã sau:
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'),
),
],
),
);
}
}
Bạn đã thêm hai tiện ích sau vào ứng dụng:
DocumentApp
thiết lập phiên bản mới nhất của Material Design để tuỳ chỉnh giao diện người dùng.DocumentScreen
cung cấp bố cục hình ảnh của trang bằng cách sử dụng tiện íchScaffold
.
- Để đảm bảo mọi thứ hoạt động trơn tru, hãy chạy ứng dụng trên máy chủ của bạn bằng cách nhấp vào Run and Debug (Chạy và gỡ lỗi):
- Theo mặc định, Flutter chọn bất kỳ nền tảng mục tiêu nào có sẵn. Để thay đổi nền tảng đích, hãy chọn nền tảng hiện tại trên Thanh trạng thái:
Bạn sẽ thấy một khung trống có phần tử title
và body
được xác định trong tiện ích DocumentScreen
:
5. Tạo và trả về bản ghi
Ở bước này, bạn sử dụng các bản ghi để trả về nhiều giá trị từ một lệnh gọi hàm. Sau đó, bạn gọi hàm đó trong tiện ích DocumentScreen
để truy cập và phản ánh các giá trị đó trong giao diện người dùng.
Tạo và trả về một bản ghi
- Trong
data.dart
, hãy thêm một phương thức getter mới vào lớp Tài liệu có tên làmetadata
. Phương thức này sẽ trả về một bản ghi:
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.
}
Loại dữ liệu trả về cho hàm này là một bản ghi có hai trường, một bản ghi thuộc loại String
và một bản ghi thuộc loại DateTime
.
Câu lệnh trả về tạo một bản ghi mới bằng cách đặt hai giá trị trong dấu ngoặc đơn, (title, modified: now)
.
Trường đầu tiên là theo vị trí và chưa được đặt tên, còn trường thứ hai là modified
.
Truy cập các trường bản ghi
- Trong tiện ích
DocumentScreen
, hãy gọi phương thức gettermetadata
trong phương thứcbuild
để bạn có thể nhận bản ghi và truy cập vào các giá trị của bản ghi:
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.
),
),
],
),
);
}
}
Phương thức getter metadata
trả về một bản ghi được gán cho biến cục bộ metadataRecord
. Bản ghi là một cách đơn giản và dễ dàng để trả về nhiều giá trị từ một lệnh gọi hàm và gán các giá trị đó cho một biến.
Để truy cập vào từng trường riêng lẻ có trong bản ghi đó, bạn có thể sử dụng phần tử bản ghi tích hợp sẵn cú pháp getter.
- Để nhận một trường vị trí (trường không có tên, chẳng hạn như
title
), hãy sử dụng phương thức getter$<num>
trên bản ghi. Thao tác này chỉ trả về các trường chưa được đặt tên. - Các trường được đặt tên như
modified
không có phương thức getter vị trí, vì vậy, bạn có thể sử dụng trực tiếp tên của phương thức đó, chẳng hạn nhưmetadataRecord.modified
.
Để xác định tên của một phương thức getter cho một trường vị trí, hãy bắt đầu từ $1
và bỏ qua các trường đã đặt tên. Ví dụ:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Tải lại nhanh để xem các giá trị JSON hiển thị trong ứng dụng. Trình bổ trợ VS Code Dart sẽ tải lại nhanh mỗi khi bạn lưu tệp.
Bạn có thể thấy rằng trên thực tế, mỗi trường đều duy trì kiểu của nó.
- Phương thức
Text()
lấy một Chuỗi làm đối số đầu tiên. - Trường
modified
là một DateTime và được chuyển đổi thànhString
bằng cách sử dụng nội suy chuỗi.
Một cách an toàn về kiểu khác để trả về các loại dữ liệu khác nhau là xác định một lớp chi tiết hơn.
6. Khớp và huỷ cấu trúc bằng các mẫu
Bản ghi có thể thu thập nhiều loại dữ liệu một cách hiệu quả và dễ dàng truyền đi. Bây giờ, hãy cải thiện mã bằng cách sử dụng mẫu.
Mẫu biểu thị cấu trúc mà một hoặc nhiều giá trị có thể nhận, chẳng hạn như sơ đồ thiết kế. Mẫu so sánh với giá trị thực tế để xác định xem chúng có khớp hay không.
Khi chúng khớp với nhau, một số mẫu sẽ huỷ cấu trúc giá trị khớp bằng cách lấy dữ liệu từ đó. Việc giải cấu trúc cho phép bạn giải nén các giá trị từ một đối tượng để gán các giá trị đó cho các biến cục bộ hoặc thực hiện so khớp thêm trên các giá trị đó.
Huỷ cấu trúc một bản ghi thành các biến cục bộ
- Tái cấu trúc phương thức
build
củaDocumentScreen
để gọimetadata
và sử dụng phương thức này để khởi chạy khai báo biến mẫu:
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
),
),
],
),
);
}
}
Mẫu bản ghi (title, modified: modified)
chứa 2 mẫu biến khớp với các trường của bản ghi do metadata
trả về.
- Biểu thức khớp với mẫu phụ vì kết quả là một bản ghi có hai trường, một trong số đó có tên là
modified
. - Vì chúng khớp nhau, mẫu khai báo biến giải cấu trúc biểu thức, truy cập vào các giá trị của biểu thức đó và liên kết chúng với các biến cục bộ mới cùng kiểu và tên,
String title
vàDateTime modified
.
Có cách viết tắt khi tên của một trường và biến điền vào trường đó giống nhau. Tái cấu trúc phương thức build
của DocumentScreen
như sau.
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',
),
),
],
),
);
}
}
Cú pháp của mẫu biến :modified
là viết tắt của modified: modified
. Nếu muốn một biến cục bộ mới có tên khác, bạn có thể viết modified: localModified
.
- Tải lại nhanh để xem kết quả tương tự như trong bước trước. Hành vi này hoàn toàn giống nhau; bạn vừa làm cho mã của mình ngắn gọn hơn.
7. Sử dụng các mẫu để trích xuất dữ liệu
Trong một số ngữ cảnh nhất định, các mẫu không chỉ khớp và huỷ cấu trúc mà còn có thể đưa ra quyết định về tác động của mã, dựa trên việc mẫu có khớp hay không. Đây được gọi là mẫu có thể từ chối.
Mẫu khai báo biến mà bạn sử dụng trong bước cuối cùng là mẫu không thể từ chối: giá trị phải khớp với mẫu, nếu không, đó là lỗi và việc huỷ cấu trúc sẽ không xảy ra. Hãy nghĩ đến bất kỳ thao tác khai báo hoặc chỉ định biến nào; bạn không thể chỉ định giá trị cho một biến nếu chúng không cùng loại.
Mặt khác, mẫu có thể điều chỉnh được dùng trong ngữ cảnh luồng điều khiển:
- Họ kỳ vọng rằng một số giá trị mà họ so sánh sẽ không khớp.
- Các giá trị này được thiết kế để ảnh hưởng đến luồng kiểm soát, dựa vào việc giá trị có khớp hay không.
- Chúng không làm gián đoạn quá trình thực thi kèm theo lỗi nếu chúng không khớp nhau mà chỉ chuyển sang câu lệnh tiếp theo.
- Chúng có thể huỷ cấu trúc và liên kết các biến chỉ sử dụng được khi chúng khớp
Đọc các giá trị JSON mà không có mẫu
Trong phần này, bạn sẽ đọc dữ liệu mà không cần so khớp mẫu để xem cách các mẫu có thể giúp bạn xử lý dữ liệu JSON như thế nào.
- Thay thế phiên bản trước đó của
metadata
bằng phiên bản đọc các giá trị của bản đồ_json
. Sao chép và dán phiên bản này củametadata
vào lớpDocument
:
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.
}
}
Mã này xác thực rằng dữ liệu có cấu trúc chính xác mà không cần sử dụng mẫu. Ở bước sau, bạn sử dụng tính năng so khớp mẫu để thực hiện cùng một quy trình xác thực mà sử dụng ít mã hơn. Bot thực hiện 3 bước kiểm tra trước khi làm bất cứ việc gì khác:
- JSON chứa cấu trúc dữ liệu mà bạn mong đợi:
if (_json.containsKey('metadata'))
- Dữ liệu có loại bạn mong đợi:
if (metadataJson is Map)
- Dữ liệu không rỗng và điều này được ngầm xác nhận trong lần kiểm tra trước đó.
Đọc các giá trị JSON bằng mẫu bản đồ
Với mẫu có thể từ chối, bạn có thể xác minh rằng JSON có cấu trúc dự kiến bằng cách sử dụng mẫu bản đồ.
- Thay thế phiên bản trước đó của
metadata
bằng mã này:
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.
}
}
Ở đây, bạn sẽ thấy một loại câu lệnh if mới (được giới thiệu trong Dart 3), là if-case. Nội dung trường hợp chỉ thực thi nếu mẫu trường hợp khớp với dữ liệu trong _json
. So khớp này thực hiện các bước kiểm tra tương tự như bạn đã viết trong phiên bản đầu tiên của metadata
để xác thực JSON sắp tới. Mã này xác thực những mục sau:
_json
là một loại Bản đồ._json
chứa khoámetadata
._json
không rỗng._json['metadata']
cũng là một loại Bản đồ._json['metadata']
chứa các khoátitle
vàmodified
.title
vàlocalModified
là các chuỗi và không rỗng.
Nếu giá trị không khớp, mẫu sẽ rút lại (từ chối tiếp tục thực thi) và chuyển sang mệnh đề else
. Nếu việc so khớp thành công, mẫu này sẽ huỷ cấu trúc các giá trị của title
và modified
khỏi bản đồ và liên kết chúng với các biến cục bộ mới.
Để có danh sách đầy đủ các mẫu, hãy xem bảng trong phần Mẫu của thông số kỹ thuật của tính năng.
8. Chuẩn bị ứng dụng để biết thêm mẫu
Cho đến nay, bạn đã xử lý phần metadata
của dữ liệu JSON. Ở bước này, bạn sẽ tinh chỉnh logic nghiệp vụ thêm một chút để xử lý dữ liệu trong danh sách blocks
và kết xuất dữ liệu đó vào ứng dụng.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Tạo một lớp lưu trữ dữ liệu
- Thêm một lớp mới là
Block
vàodata.dart
. Lớp này dùng để đọc và lưu trữ dữ liệu cho một trong các khối trong dữ liệu 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');
}
}
}
Hàm khởi tạo nhà máy fromJson()
sử dụng cùng một trường hợp if-case với một mẫu ánh xạ mà bạn đã thấy trước đây.
Lưu ý rằng json
khớp với mẫu bản đồ, mặc dù một trong các khoá checked
không được tính trong mẫu. Mẫu bản đồ bỏ qua mọi mục nhập trong đối tượng ánh xạ không được tính rõ ràng trong mẫu.
Trả về danh sách đối tượng Chặn
- Tiếp theo, hãy thêm một hàm mới là
getBlocks()
vào lớpDocument
.getBlocks()
phân tích cú pháp JSON thành các thực thể của lớpBlock
và trả về danh sách các khối để kết xuất trong giao diện người dùng của bạn:
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.
}
Hàm getBlocks()
trả về danh sách đối tượng Block
mà sau này bạn sử dụng để tạo giao diện người dùng. Câu lệnh if-case quen thuộc sẽ thực hiện việc xác thực và truyền giá trị của siêu dữ liệu blocks
vào một List
mới có tên là blocksJson
(không có mẫu, bạn sẽ cần dùng phương thức toList()
để truyền).
Hằng danh sách chứa một bộ sưu tập cho để điền các đối tượng Block
vào danh sách mới.
Phần này không giới thiệu tính năng liên quan đến mẫu mà bạn chưa thử trong lớp học lập trình này. Trong bước tiếp theo, bạn sẽ chuẩn bị hiển thị các mục trong danh sách trong giao diện người dùng.
9. Sử dụng các mẫu để hiển thị tài liệu
Bây giờ, bạn đã huỷ cấu trúc và kết hợp lại dữ liệu JSON thành công bằng cách sử dụng câu lệnh if-case và các mẫu có thể từ chối. Nhưng nếu-case-case chỉ là một trong các cải tiến để kiểm soát cấu trúc luồng đi kèm với mẫu. Bây giờ, bạn sẽ áp dụng kiến thức về các mẫu có thể từ chối để chuyển đổi câu lệnh.
Kiểm soát nội dung được hiển thị bằng cách sử dụng các mẫu có câu lệnh chuyển đổi
- Trong
main.dart
, hãy tạo một tiện ích mới làBlockWidget
. Tiện ích này sẽ xác định kiểu của từng khối dựa trên trườngtype
của khối đó.
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,
),
);
}
}
Câu lệnh chuyển đổi trong phương thức build
chuyển đổi trên trường type
của đối tượng block
.
- Câu lệnh trường hợp đầu tiên sử dụng mẫu chuỗi không đổi. Mẫu này khớp nếu
block.type
bằng giá trị không đổih1
. - Câu lệnh trường hợp thứ hai sử dụng mẫu-hoặc logic với hai mẫu chuỗi không đổi làm mẫu phụ. Mẫu này khớp nếu
block.type
khớp với một trong hai mẫu phụp
hoặccheckbox
.
- Trường hợp cuối cùng là mẫu ký tự đại diện,
_
. Ký tự đại diện trong trường hợp chuyển đổi khớp với mọi nội dung khác. Các mệnh đề này hoạt động giống như mệnh đềdefault
nhưng vẫn được phép sử dụng trong các câu lệnh chuyển đổi (chỉ chi tiết hơn một chút).
Bạn có thể dùng mẫu ký tự đại diện ở bất cứ nơi nào cho phép mẫu – ví dụ: trong một mẫu khai báo biến: var (title, _) = document.metadata;
Trong trường hợp này, ký tự đại diện không ràng buộc bất kỳ biến nào. Thao tác này sẽ loại bỏ trường thứ hai.
Trong phần tiếp theo, bạn sẽ tìm hiểu về các tính năng chuyển đổi khác sau khi hiển thị các đối tượng Block
.
Hiển thị nội dung tài liệu
Tạo một biến cục bộ chứa danh sách đối tượng Block
bằng cách gọi getBlocks()
trong phương thức build
của tiện ích DocumentScreen
.
- Thay thế phương thức
build
hiện tại trongDocumentationScreen
bằng phiên bản này:
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.
],
),
);
}
}
Dòng BlockWidget(block: blocks[index])
tạo một tiện ích BlockWidget
cho mỗi mục trong danh sách các khối được trả về từ phương thức getBlocks()
.
- Chạy ứng dụng và bạn sẽ thấy các khối xuất hiện trên màn hình:
10. Sử dụng biểu thức chuyển đổi
Mẫu bổ sung nhiều chức năng cho switch
và case
. Để có thể sử dụng chúng ở nhiều nơi hơn, Dart đã chuyển đổi biểu thức. Một loạt các trường hợp có thể trực tiếp cung cấp giá trị cho câu lệnh trả về hoặc lệnh gán biến.
Chuyển đổi câu lệnh chuyển đổi thành biểu thức chuyển đổi
Trình phân tích Dart cung cấp các tính năng hỗ trợ để giúp bạn thay đổi mã.
- Di chuyển con trỏ đến câu lệnh chuyển đổi từ phần trước.
- Nhấp vào bóng đèn để xem các trợ lý hiện có.
- Chọn hỗ trợ Chuyển đổi sang biểu thức chuyển đổi.
Phiên bản mới của mã này sẽ có dạng như sau:
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,
),
);
}
}
Biểu thức chuyển đổi trông tương tự như câu lệnh chuyển đổi, nhưng sẽ loại bỏ từ khoá case
và sử dụng =>
để tách mẫu khỏi phần thân. Không giống như câu lệnh chuyển đổi, biểu thức chuyển đổi trả về một giá trị và có thể được sử dụng ở bất kỳ nơi nào có thể sử dụng biểu thức.
11. Sử dụng các mẫu đối tượng
Dart là ngôn ngữ hướng đối tượng, do đó, mẫu áp dụng cho mọi đối tượng. Ở bước này, bạn bật mẫu đối tượng và huỷ cấu trúc các thuộc tính đối tượng để cải thiện logic hiển thị ngày trên giao diện người dùng.
Trích xuất thuộc tính từ các mẫu đối tượng
Trong phần này, bạn cải thiện cách hiển thị ngày sửa đổi gần nhất bằng cách sử dụng các mẫu.
- Thêm phương thức
formatDate
vàomain.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',
};
}
Phương thức này trả về một biểu thức chuyển đổi bật giá trị difference
, đối tượng Duration
. Thuộc tính này thể hiện khoảng thời gian từ today
đến modified
trong dữ liệu JSON.
Mỗi trường hợp của biểu thức chuyển đổi sẽ sử dụng một mẫu đối tượng phù hợp bằng cách gọi phương thức getter trên các thuộc tính inDays
và isNegative
của đối tượng. Cú pháp có vẻ như đang tạo đối tượng Duration nhưng thực tế lại truy cập vào các trường trên đối tượng difference
.
Ba trường hợp đầu tiên sử dụng các mẫu con không đổi 0
, 1
và -1
để khớp với thuộc tính đối tượng inDays
và trả về chuỗi tương ứng.
Hai trường hợp gần đây nhất xử lý khoảng thời gian vượt quá hôm nay, hôm qua và ngày mai:
- Nếu thuộc tính
isNegative
khớp với mẫu hằng số booleantrue
, nghĩa là ngày sửa đổi nằm trong quá khứ, thì thuộc tính đó sẽ hiển thị ngày trước. - Nếu trường hợp đó không xác định được sự khác biệt, thì thời hạn phải là số ngày dương (không cần xác minh rõ ràng bằng
isNegative: false
). Vì vậy, ngày sửa đổi là ngày trong tương lai và hiển thị số ngày kể từ bây giờ.
Thêm logic định dạng cho tuần
- Thêm hai trường hợp mới vào hàm định dạng để xác định khoảng thời gian dài hơn 7 ngày sao cho giao diện người dùng có thể hiển thị các trường hợp đó dưới dạng tuần:
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',
};
}
Mã này giới thiệu các mệnh đề bảo vệ:
- Mệnh đề bảo vệ sử dụng từ khoá
when
sau mẫu trường hợp. - Có thể dùng các hàm này trong trường hợp if, câu lệnh chuyển đổi và biểu thức chuyển đổi.
- Phương thức này chỉ thêm điều kiện vào mẫu sau khi mẫu được so khớp.
- Nếu mệnh đề bảo vệ có giá trị false, thì toàn bộ mẫu sẽ bị rút lại và quá trình thực thi sẽ chuyển sang trường hợp tiếp theo.
Thêm ngày mới được định dạng vào giao diện người dùng
- Cuối cùng, hãy cập nhật phương thức
build
trongDocumentScreen
để sử dụng hàmformatDate
:
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]);
},
),
),
],
),
);
}
}
- Tải lại nhanh để xem các thay đổi trong ứng dụng của bạn:
12. Đóng dấu một lớp để chuyển đổi toàn diện
Lưu ý rằng bạn không sử dụng ký tự đại diện hoặc trường hợp mặc định ở cuối nút chuyển gần đây nhất. Mặc dù nên luôn đưa vào trường hợp các giá trị có thể vượt qua, nhưng bạn cũng có thể sử dụng ví dụ đơn giản như thế này vì bạn biết các trường hợp bạn đã xác định có tính đến tất cả giá trị có thể có inDays
có thể nhận.
Khi mọi trường hợp trong một nút chuyển được xử lý, quá trình này được gọi là nút chuyển toàn diện. Ví dụ: việc bật một loại bool
đã hoàn tất khi gặp các trường hợp cho true
và false
. Việc bật loại enum
là chưa đầy đủ khi có trường hợp cho từng giá trị của enum, vì enum đại diện cho số cố định của các giá trị không đổi.
Dart 3 mở rộng phạm vi kiểm tra tính toàn diện cho các đối tượng và hệ phân cấp lớp bằng đối tượng sửa đổi lớp mới sealed
. Tái cấu trúc lớp Block
dưới dạng một lớp cấp cao kín.
Tạo lớp con
- Trong
data.dart
, hãy tạo 3 lớp mới –HeaderBlock
,ParagraphBlock
vàCheckboxBlock
– mở rộngBlock
:
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);
}
Mỗi lớp này tương ứng với các giá trị type
khác nhau từ JSON gốc: 'h1'
, 'p'
và 'checkbox'
.
Ấn tượng đẳng cấp cao cấp
- Đánh dấu lớp
Block
làsealed
. Sau đó, hãy tái cấu trúc trường hợp if dưới dạng biểu thức chuyển đổi để trả về lớp con tương ứng vớitype
được chỉ định trong 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'),
};
}
}
Từ khoá sealed
là một đối tượng sửa đổi lớp, có nghĩa là bạn chỉ có thể mở rộng hoặc triển khai lớp này trong cùng một thư viện. Vì biết các loại phụ của lớp này, nên trình phân tích sẽ báo cáo lỗi nếu nút chuyển không bao gồm được một trong các loại phụ đó và không đầy đủ.
Sử dụng một biểu thức chuyển đổi để hiển thị tiện ích
- Cập nhật lớp BlockWidget trong
main.dart
bằng một biểu thức chuyển đổi sử dụng các mẫu đối tượng cho từng trường hợp:
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),
],
),
},
);
}
}
Trong phiên bản đầu tiên của BlockWidget
, bạn đã bật một trường của đối tượng Block
để trả về một TextStyle
. Bây giờ, bạn sẽ chuyển đổi một thực thể của chính đối tượng Block
và so khớp với các mẫu đối tượng đại diện cho các lớp con của nó, trích xuất các thuộc tính của đối tượng trong quy trình này.
Trình phân tích Dart có thể kiểm tra để đảm bảo mỗi lớp con được xử lý trong biểu thức chuyển đổi vì bạn đã đặt Block
làm lớp kín.
Ngoài ra, xin lưu ý rằng việc sử dụng biểu thức chuyển đổi ở đây cho phép bạn truyền kết quả trực tiếp đến phần tử child
, thay vì câu lệnh trả về riêng biệt cần thiết trước đó.
- Tải lại nóng để xem dữ liệu JSON của hộp đánh dấu được kết xuất lần đầu tiên:
13. Xin chúc mừng
Bạn đã thử nghiệm thành công với các mẫu, bản ghi, nút chuyển nâng cao và hộp đựng cũng như các lớp kín. Bạn đã đề cập đến rất nhiều thông tin, nhưng chỉ mới sử dụng được phần nổi bật của những tính năng này. Để biết thêm thông tin về mẫu, hãy xem quy cách tính năng.
Các loại mẫu khác nhau, các ngữ cảnh khác nhau mà chúng có thể xuất hiện và sự lồng ghép có thể của các mẫu con khiến khả năng hành vi dường như vô tận. Tuy nhiên, những hình ảnh đó rất dễ thấy.
Bạn có thể tưởng tượng vô số cách thức hiển thị nội dung trong Flutter bằng các mẫu. Khi sử dụng các mẫu, bạn có thể trích xuất dữ liệu một cách an toàn để xây dựng giao diện người dùng chỉ bằng vài dòng mã.
Tiếp theo là gì?
- Hãy xem tài liệu về mẫu, bản ghi, trường hợp và nút chuyển nâng cao cũng như đối tượng sửa đổi lớp trong phần Ngôn ngữ của tài liệu về Dart.
Tài liệu tham khảo
Xem mã mẫu đầy đủ theo từng bước trong kho lưu trữ flutter/codelabs
.
Để biết thông số kỹ thuật chuyên sâu cho từng tính năng mới, hãy xem các tài liệu thiết kế gốc: