Lập trình Flutter với ngôn ngữ lập trình Dart cơ bản.pdf

hu1yhuy 234 views 190 slides Mar 16, 2025
Slide 1
Slide 1 of 303
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81
Slide 82
82
Slide 83
83
Slide 84
84
Slide 85
85
Slide 86
86
Slide 87
87
Slide 88
88
Slide 89
89
Slide 90
90
Slide 91
91
Slide 92
92
Slide 93
93
Slide 94
94
Slide 95
95
Slide 96
96
Slide 97
97
Slide 98
98
Slide 99
99
Slide 100
100
Slide 101
101
Slide 102
102
Slide 103
103
Slide 104
104
Slide 105
105
Slide 106
106
Slide 107
107
Slide 108
108
Slide 109
109
Slide 110
110
Slide 111
111
Slide 112
112
Slide 113
113
Slide 114
114
Slide 115
115
Slide 116
116
Slide 117
117
Slide 118
118
Slide 119
119
Slide 120
120
Slide 121
121
Slide 122
122
Slide 123
123
Slide 124
124
Slide 125
125
Slide 126
126
Slide 127
127
Slide 128
128
Slide 129
129
Slide 130
130
Slide 131
131
Slide 132
132
Slide 133
133
Slide 134
134
Slide 135
135
Slide 136
136
Slide 137
137
Slide 138
138
Slide 139
139
Slide 140
140
Slide 141
141
Slide 142
142
Slide 143
143
Slide 144
144
Slide 145
145
Slide 146
146
Slide 147
147
Slide 148
148
Slide 149
149
Slide 150
150
Slide 151
151
Slide 152
152
Slide 153
153
Slide 154
154
Slide 155
155
Slide 156
156
Slide 157
157
Slide 158
158
Slide 159
159
Slide 160
160
Slide 161
161
Slide 162
162
Slide 163
163
Slide 164
164
Slide 165
165
Slide 166
166
Slide 167
167
Slide 168
168
Slide 169
169
Slide 170
170
Slide 171
171
Slide 172
172
Slide 173
173
Slide 174
174
Slide 175
175
Slide 176
176
Slide 177
177
Slide 178
178
Slide 179
179
Slide 180
180
Slide 181
181
Slide 182
182
Slide 183
183
Slide 184
184
Slide 185
185
Slide 186
186
Slide 187
187
Slide 188
188
Slide 189
189
Slide 190
190
Slide 191
191
Slide 192
192
Slide 193
193
Slide 194
194
Slide 195
195
Slide 196
196
Slide 197
197
Slide 198
198
Slide 199
199
Slide 200
200
Slide 201
201
Slide 202
202
Slide 203
203
Slide 204
204
Slide 205
205
Slide 206
206
Slide 207
207
Slide 208
208
Slide 209
209
Slide 210
210
Slide 211
211
Slide 212
212
Slide 213
213
Slide 214
214
Slide 215
215
Slide 216
216
Slide 217
217
Slide 218
218
Slide 219
219
Slide 220
220
Slide 221
221
Slide 222
222
Slide 223
223
Slide 224
224
Slide 225
225
Slide 226
226
Slide 227
227
Slide 228
228
Slide 229
229
Slide 230
230
Slide 231
231
Slide 232
232
Slide 233
233
Slide 234
234
Slide 235
235
Slide 236
236
Slide 237
237
Slide 238
238
Slide 239
239
Slide 240
240
Slide 241
241
Slide 242
242
Slide 243
243
Slide 244
244
Slide 245
245
Slide 246
246
Slide 247
247
Slide 248
248
Slide 249
249
Slide 250
250
Slide 251
251
Slide 252
252
Slide 253
253
Slide 254
254
Slide 255
255
Slide 256
256
Slide 257
257
Slide 258
258
Slide 259
259
Slide 260
260
Slide 261
261
Slide 262
262
Slide 263
263
Slide 264
264
Slide 265
265
Slide 266
266
Slide 267
267
Slide 268
268
Slide 269
269
Slide 270
270
Slide 271
271
Slide 272
272
Slide 273
273
Slide 274
274
Slide 275
275
Slide 276
276
Slide 277
277
Slide 278
278
Slide 279
279
Slide 280
280
Slide 281
281
Slide 282
282
Slide 283
283
Slide 284
284
Slide 285
285
Slide 286
286
Slide 287
287
Slide 288
288
Slide 289
289
Slide 290
290
Slide 291
291
Slide 292
292
Slide 293
293
Slide 294
294
Slide 295
295
Slide 296
296
Slide 297
297
Slide 298
298
Slide 299
299
Slide 300
300
Slide 301
301
Slide 302
302
Slide 303
303

About This Presentation

Flutter


Slide Content

tailieusharefree.blogspot.com

Lập trình Flutter với ngôn ngữ lập
trình Dart cơ bản

Flutter là một framework mã nguồn mở cho phép tạo ứng dụng
di động với hiệu năng cao, chất lượng tốt hỗ trợ đa nền tảng, phù
hợp với phát triển ứng dụng Android và iOS.
Sử dụng ngôn ngữ Dart của chính Google, Flutter rất dễ học,
mạnh mẽ, hiệu năng cao và phát triển ứng dụng di động một cách
nhanh chóng.
Trong khoá học này, mình sẽ giúp các bạn làm quen với Flutter
framework, hướng dẫn cài đặt Flutter SDK, thiết lập Android
Studio để xây dựng một ứng dụng Flutter căn bản, nắm vững kỹ
thuật của Flutter framework và có khả năng phát triển các loại
ứng dụng khác nhau sử dụng Flutter framework.




-------------------------------------------------
Thanh Tran
Copyright © 2021 by Su TP. All Right Reserved.

tailieusharefree.blogspot.com

T???? ?? C???????

1. Bài 1: Giới thiệu Flutter
2. Bài 2: Cài đặt Flutter
3. Bài 3: Tạo ứng dụng Flutter đầu tiên
4. Bài 4: Kiến trúc ứng dụng Flutter
5. Bài 5: Giới thiệu ngôn ngữ Dart
6. Bài 6: Widget trong Flutter
7. Bài 7: Layout trong Flutter
8. Bài 8: Gesture trong Flutter
9. Bài 9: Quản lý trạng thái Sate trong Flutter
10. Bài 10: Statefulwidget trong Flutter
11. Bài 11: ScopedModel trong Flutter
12. Bài 12: Navigator và Routing
13. Bài 13: Animation
14. Bài 14: Code với native Android
15. Bài 15: Code với native IOS
16. Bài 16: Giới thiệu về package
17. Bài 17: REST API
18. Bài 18: Khái niệm về Databasetailieusharefree.blogspot.com

19. Bài 19: Chuyển đổi ngôn ngữ
20. Bài 20: Testing
21. Bài 21: Xuất ứng dụng trong Flutter
22. Bài 22: Công cụ phát triển
23. Bài 23: Viết ứng dụng hoàn chỉnh
tailieusharefree.blogspot.com

Bài 1: Giới thiệu Flutter
F????~? ?? ???
Flutter là một framework mã nguồn mở cho phép tạo ứng dụng
di động với hiệu năng cao, chất lượng tốt hỗ trợ đa nền tảng, phù
hợp với phát triển ứng dụng Android và iOS.
Sử dụng ngôn ngữ Dart của chính Google, Flutter rất dễ học,
mạnh mẽ, hiệu năng cao và phát triển ứng dụng di động một cách
nhanh chóng.
Trong khoá học này, mình sẽ giúp các bạn làm quen với Flutter
framework, hướng dẫn cài đặt Flutter SDK, thiết lập Android
Studio để xây dựng một ứng dụng Flutter căn bản, nắm vững kỹ
thuật của Flutter framework và có khả năng phát triển các loại
ứng dụng khác nhau sử dụng Flutter framework.
G?ớ? ???ệ? F????~?
Nhìn chung phát triển ứng dụng di động là một công việc phức
tạp và nhiều khó khăn. Có rất nhiều framework hỗ trợ bạn phát
triển một ứng dụng mobile. Android cung cấp một framework cơ
bản dựa trên ngôn ngữ lập trình Java còn iOS thì cung cấp
framework dựa trên Objective-C / Swift
Tuy nhiên hầu hết các ứng dụng hiện nay, đều hỗ trợ cả 2 nền
tảng Android và iOS, do đó cùng lúc phát triển 2 dự án khác
nhau với 2 framework khác nhau là một công việc phức tạp vàtailieusharefree.blogspot.com

lãng phí thời gian công sức. Do đó người ta đã phát triển các
framework lập trình đa nền tảng để giải quyết vấn đề này. Một
framework rất phổ biến hiện nay là React Native được phát triển
bới Facebook đang được sử dụng rất rộng rãi. Tuy nhiên React
Native vẫn thông qua các api của các framework gốc như
Android hay iOS do đó bị hạn chế và tốc độ kém.
Như một sự phát triển của tương lai, Flutter được phát triển bới
chính Google, đơn vị sở hữu Android như một đối trọng trực tiếp
với React Native. Thay vì gọi các api của framework gốc, Flutter
tạo ra giao diện trực tiếp từ api của hệ điều hành. Nhờ đó ứng
dụng sẽ chạy nhanh hơn, mượt mà hơn và đẹp hơn.
Flutter cung cấp rất nhiều widgets (UI) là các thành phần đồ hoạ
được thiết kế riêng. Những đối tượng đồ hoạ này được tối ưu phù
hợp với môi trường mobile và dễ dàng trong việc thiết kế như
HTML.
Cụ thể, ứng dụng Flutter sẽ sử dụng các widget riêng. Flutter
widgets cung cấp các animations (hiệu ứng) và gestures (thao tác)
riêng. Ứng dụng được phát triển dựa trên logic của reactive
programming. Mỗi Widget sẽ có rất nhiều trang thái. Bằng cách
thay đổi trạng thái của widget, Flutter sẽ tự động (reactive
programming) so sánh trạng thái của widget (cũ và mới) để tạo ra
những thay đổi cần thiết thay vì khởi tạo lại cả đối tượng.
Mình sẽ nói kỹ hơn về kĩ thuật này trong các bài tiếp theo
T. = ủ F
Flutter framework có những đặc điểm sautailieusharefree.blogspot.com

1. Hiện đã và là một react framework
2. Sử dụng ngôn ngữ lập trình Dart đơn giản và dễ học
3. Phát triển ứng dụng nhanh
4. Giao diện người dùng rất đẹp và linh hoạt
5. Hỗ trợ rất nhiều widget khác nhau
6. Thể hiện cùng một UI trên nhiều nền tảng
7. Ứng dụng có hiệu năng cao
Đ ể ạ ủ F
Flutter đi kèm với nhiều widget đẹp và có độ tuỳ biến cao giúp
phát triển ứng dụng hiệu năng cao vượt trội đáp ứng mọi nhu cầu
và tuỳ biến. Bên cạnh đó Flutter còn có những điểm mạnh sau:
1. Dart có một kho lớn các gói phần mềm cho phép bạn
mở rộng khả năng cho ứng dụng của mình
2. Các lập trình viên chỉ cần viết một chương trình duy
nhất cho tất cả các ứng dụng (Android và iOS)
. Flutter có thể mở rộng ra các nền tảng khác trong thời
gian tới.
3. Flutter dễ dàng kiểm thử hơn do tiết kiệm thời gian
kiểm thử trên từng nền tảng.
4. Nhờ sự đơn giản của mình, Flutter là lựa chọn hàng đầu
cho các ứng dụng mới. Nó còn dễ dàng tuỳ biến và mở
rộng lên càng mạnh mẽ hơn
5. Với Flutter, lập trình viên có toàn quyền để sắp xếp bổ
trí điều khiển các widgettailieusharefree.blogspot.com

6. Flutter có bộ công cụ phát triển (developer tools) rất
hoàn thiện và đầy đủ, đặc biệt với tính năng hot reload
đẩy nhanh tốc độ build ứng dụng đáng kinh ngạc
Đố? ?ượ?? ??z? ??z ???? ?ọ|
Khoá học dành cho các bạn mới bắt đầu làm quen với Flutter và
lập trình di động. Tuy nhiên nếu bạn đã biết lập trình Android
hoặc IOS native sẽ có lợi thế nhiều hơn.
Ngoài ra các bạn cần có kiến thức cơ bản về lập trình như tin học
đại cương, lập trình C hoặc Java
tailieusharefree.blogspot.com

Bài 2: Cài đặt Flutter

Trong bài học này, mình sẽ hưỡng dẫn các bạn cài đặt Flutter
framework trên máy tính cá nhân để chuẩn bị môi trường học
Flutter
C?? ?ặ? F????~? ???? W??}??
Các bước cài đặt Flutter SDK trên Window
Bước 1 − Các bạn truy cập địa chỉ , https://flutter.dev/docs/get-
started/install/windows và tải phiên bản mới nhất của Flutter
SDK. Hôm nay 08/04/2020 phiên bản mới nhất là 1.12.13 và file
tải về là flutter_windows_v1.12.13+hotfix.9-stable.zip
Bước 2 − Giải nén vô thư mục bất kì ví dụ C:\flutter\
Bước 3 − Cập nhật system path cho thư mục flutter\bin
Trong thanh tìm kiếm ở Start, bạn gõ ‘env’ sau đó chọn Edit
environment variables for your account.. Dưới dòng chữ User
variables bạn kiểm tra nếu thấy ô Path:, thì thêm đường dẫn đầy
đủ của thư mục flutter\bin sử dụng dấu ; để ngăn cách với các
biến khác.
Bước 4 − Flutter cung cấp một tool gọi là flutter doctor để
kiểm tra tất cả những yêu cầu cần thiết cho môi trường phát triển
Flutter
flutter doctortailieusharefree.blogspot.com

Bước 5 − Các bạn chạy lệnh phía trên để hệ thống kiểm tra và
đưa ra báo cáo như sau
Doctor summary (to see all details, run flutter
doctor -v):
[√] Flutter (Channel stable, v1.2.1, on Microsoft
Windows [Version
10.0.17134.706], locale en-US)
[√] Android toolchain - develop for Android
devices (Android SDK version
28.0.3)
[√] Android Studio (version 3.2)
[√] VS Code, 64-bit edition (version 1.29.1)
[!] Connected device
! No devices available
! Doctor found issues in 1 category.
Như thông báo ở trên ta có thấy rằng Flutter SDK đã được cài,
Android Tool đã được cài , Android Stuido đã được cài, chưa có
kết nối tới thiết bị, bạn cần kết nối thiết bị điện thoại qua USB
hoặc bật máy ảo
Bước 6 − Cài đặt bản Android SDK mới nhất nếu bạn chưa cài
đặt và được cảnh báo bởi flutter doctor
Bước 7 − Cài đặt Android Studio mới nhất nếu bạn chưa cài đặt
và được cảnh báo
Bước 8 − Bật android emulator hoặc kết nối tới một thiết bị
Android
Bước 9 − Cài đặt plugin Flutter và Dart cho Android Studio. Hai
plugin này sẽ cung cấp các template để tạo ứng dụng Flutter vàtailieusharefree.blogspot.com

các tuỳ chọn để chạy và debug ứng dụng Flutter trên Android
studio
1. Mở Android Studio.
2. Chọn File → Settings → Plugins.
3. Tìm kiếm Flutter plugin và click vào Install.
4. Chọn Yes khi hệ thống yêu cầu cài đặt Dart plugin.
5. Khởi động lại Android studio.
C?? ?ặ? F????~? ?? Mz|OS
Để cài đặt Flutter SDK trên MacOS, các bạn thực hiện theo các
bước sau
Bước 1 − Truy cập địa chỉ URL, https://flutter.dev/docs/get-
started/install/macos và tải về phiên bản Flutter SDK mới nhất
Bước 2 − Giải nén vô thư mục bất kì /path/to/flutter
Bước 3 − Cập nhật system path bao gồm thư mục flutter bin (ở
trong ~/.bashrc file). bằng cách chạy lệnh sau
> export PATH = "$PATH:/path/to/flutter/bin"
Bước 4 − Update lại hệ thống và kiểm tra Path bằng lênh sau
source ~/.bashrc
source $HOME/.bash_profile
echo $PATH
Flutter cung cấp một tool, flutter doctor dùng để kiểm tra các yêu
cầu cho Fullter tương tự bên Windows.
Bước 5 − Cài đặt bản mới nhất XCode nếu được yêu cầu bởi
flutter doctortailieusharefree.blogspot.com

Bước 6 − Cài đặt Android SDK nếu được yêu cầu bởi flutter
doctor
Bước 7 − Cài đặt bản mới nhất Android Studio, nếu được yêu
cầu bởi flutter doctor
Bước 8 − Bật máy ảo android emulator hoặc kết nối tới thiết bị
Android nếu bạn phát triển ứng dụng Android
Bước 9 − Bật iOS simulator hoặc kết nối tới thiết bị iPhone nếu
bạn phát triển ứng dụng iOS
Bước 10 − Cài đặt Flutter và Dart plugin cho Android Studio
tương tự như trên
Như vậy là mình đã hướng dẫn các bạn cài đặt Flutter SDK cho
máy tính cá nhân, ở bài sau chúng ta sẽ viết ứng dụng đầu tiên
với Flutter
tailieusharefree.blogspot.com

Bài 3: Tạo ứng dụng Flutter đầu
tiên

Trong bài này, mình sẽ hướng dẫn các bạn tạo một Flutter
Application đầu tiên trên Android Studio, quan đó giúp các bạn
hiểu cơ bản cấu trúc của một project ứng dụng Flutter
Bước 1 − Mở Android Studio
Bước 2 − Tạo Flutter Project mới: Chon Start a New Flutter
Project hoặc từ menu File → New → New Flutter Project
Bước 3 − Có nhiều loại proect Flutter khác nhau chúng ta
chọn Flutter Application và nhấn Next.tailieusharefree.blogspot.com

Bước 4 − Đặt tên và mô tả cho Project sau đó chọn Next.
Bạn đặt tên cho project là hello_app hoặc tên bất kì. Chọn đường
dẫn thư mục Flutter SDK, nơi lưu trữ project và mô tả của ứng
dụng
Bước 5 − Điền package_name cho ứng dụngtailieusharefree.blogspot.com

Bước 6 − Nhấn Finish và đợi một lúc để Android Studio tiến
hành việc tạo project
Sau khi tạo xong, chúng ta có thể thấy cấu trúc của một project
Flutter như bên dưới
Mình giải thích qua các thành phần của một Project Flutter
android − Thư mục code sinh tự động cho ứng dụng
Android
ios − Thư mục code sinh tự động cho ứng dụng iOS
lib − Main folder chứa Dart code được viết khi sử dụng
flutter frameworktailieusharefree.blogspot.com

ib/main.dart − File đầu tiên là điểm khởi đầu của ứng
dụng Flutter application
test − Folder chứa Dart code để test flutter application
test/widget_test.dart − Sample code
.gitignore − Git version control file - File này chứa cấu
hình cho project git
.metadata − sinh tự động bởi flutter tools
.packages − sinh tự động để theo dõi flutter packages
.iml − project file của Android studio
pubspec.yaml − Được sử dụng Pub, Flutter package
manager
pubspec.lock − Sinh tự động bởi Flutter package
manager, Pub
README.md − Project description được viết theo cấu
trúc Markdown
Bước 7 − Mặc định Android Studio đã tạo sẵn cho chúng ta code
ở lib/main.dart file , tuy nhiên chúng ta xoá đi và viết lại đoạn
code dưới đây để dễ hiểu hơn
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {tailieusharefree.blogspot.com

return MaterialApp(
title: 'Hello World Demo Application',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child:
Text(
'Hello World',
)
),
);
}tailieusharefree.blogspot.com

}
Mình sẽ giải thích ý nghĩa chi tiết của đoạn code trên
Dòng 1 − import flutter package, tên
là material. Material là một flutter package được sử
dụng để tạo giao diện người dùng theo Material design
cho Android.
Dòng 2 − là điểm khởi đầu của Flutter application là
hàm main của ứng dụng. Phương thức runApp được
gọi và truyền vào đối tượng của lớp MyApp. Mục đích
chính của phương thức runApp là đưa giao diện widget
vào hiển thị trên màn hình
Dòng 5-17 − Widget được sử dụng để tạo UI (giao diện
người dùng) qua flutter framework. StatelessWidget là
một widget, nó không bao gồm trạng thái nào của
widget. MyApp kế thừa StatelessWidget và ghi đè
phương thức build method. Mục đích của phương
thức build là tạo một phần UI cho ứng dụng. Ở đây,
phương thức build sử dụng MaterialApp, một widget
để tạo layout UI gốc cho ứng dụng. Bao gồm 3 thành
phần chính là - title (tiêu đề), theme (chủ
đề) và home (trang chủ hay phần màn hình).title là
tiêu đề của ứng dụngtheme là chủ đề của widget. Ở đây,
chúng ta set chủ đề là blue đó là màu sắc chủ đạo của
ứng dụng thông qua class ThemeData và các thuộc
tính của nó ví dụ primarySwatch.home phần màn hìnhtailieusharefree.blogspot.com

của ứng dụng, nó được tạo bởi một widget
khác, MyHomePage
Dòng 19 - 38 − MyHomePage tương tự MyApp ngoại
trừ nó trả về Scaffold Widget. Scaffold là môt top
level widget đứng sau MaterialApp widget được sử
dụng để tạo UI theo material design. Nó có hai thuộc
tính quan trọng nhất là appBar để hiển thị phần đầu
của ứng dụng và body để hiển thị nôi dung chính của
ứng dụng. AppBar là một widget khác để tạo phần đầu
của ứng dụng và sử dụng các thuộc tính của appBar.
Trong các thuộc tính của body, chúng ta sử dụng
một Center widget, đây là một widget con. Text là một
widget cuối cùng, phổ biến để hiển thị một văn bản
giữa màn hình
Bước 8 − Bây giờ chúng ta bắt đầu chạy ứng dụng bằng
lệnh Run → Run main.dart
Lưu ý bạn cần kết nối với một thiết bị Android thật thông qua
cab USB (bật chế độ nhà phát triển) hoặc kết nối với một máy aỏ
Android Emulator
Trong trường hợp Android Studio báo lỗi không kết nối được với
thiết bị (flutter run: No connected devices) có thể là do bạntailieusharefree.blogspot.com

chưa chọn API Android cho project
Các bạn chọn File → Project Structure và thêm vào SDK
Android mới nhất

Kết quả khi chạy Project Flutter trên máy ảo Android Emulator

Như vậy là mình đã hướng dẫn các bạn tạo ứng dụng Flutter
đầu tiên, giải thích các thành phần chính của project cũng nhưtailieusharefree.blogspot.com

code của file main. Trong bài tiếp theo mình sẽ hướng dẫn các
bạn tìm hiểu kiến trúc của ứng dụng Flutter
tailieusharefree.blogspot.com

Bài 4: Kiến trúc ứng dụng Flutter


Trong bài học này, chúng ta sẽ tìm hiểu kiến trúc của ứng dụng
Flutter (architecture of the Flutter framework.)
W?}?~??
Khái niệm cốt lõi nhất trong Flutter framework đó
là Trong Flutter, mọi thứ đều là widget. Widget (tiện tích) là
thành phần giao diện cơ bản nhất tạo nên toàn bộ giao diện người
dùng của ứng dụng.
Trong Flutter, bản thân chính ứng dụng đã là một widget. Mỗi
ứng dụng chính là một top-level widget và nó bao gồm một
hoặc nhiều các widget con, mỗi widget này lại có thể bao gồm
một hoặc nhiều widget con khác. Nhờ sự kết hợp linh hoạt này
chúng ta có thể tạo ra bất kì ứng dụng phức tạp nào.
Ví dụ, chúng ta có thể nhìn vào cấu trúc widget của ứng
dụng hello world (được học ở bài trước) thông qua sơ đồ dưới
đây:tailieusharefree.blogspot.com

Giải thích sơ đồ trên
MyApp là một widget được tạo ra bằng widget gốc của
Flutter, MaterialApp.
MaterialApp có các thuộc tính của màn hình home và
mô tả giao diện người dùng, nó lại được tạo ra bởi một
widget khác, MyHomePage.
MyHomePage được tạo bởi một widget gốc của
flutter, Scaffold
Scaffold có 2 thuộc tính – body và appBar
body chứa giao diện chính còn appBar chứa phần đầu
(header) của ứng dụng
Header UI là một widget gốc của
flutter, AppBar và Body UI sử dụng Center widget.
Center widget có một thuộc tính, Child, nó chứa phần
nội dung chính là một Text widget
G~????~?tailieusharefree.blogspot.com

Flutter widget hỗ trợ tương tác thông qua một widget đặc biệt gọi
là GestureDetector. GestureDetector là một tiện ích không
hiển thị trên giao diện nhưng có khả năng nắm bắt các thao tác
của người dùng như nhấp, kéo, vuốt, chạm.... Phần lớn widget
gốc của Flutter hỗ trợ tương tác giao diện thông
qua GestureDetector. Chúng ta sẽ tìm hiểu chi tiết về gesture
(cử chỉ) trong các bài học tiếp theo.
K??? ??ệ? S?z?~
Flutter widgets quản lý các State (trạng thái) thông qua một
widget đặc biệt StatefulWidget. Tất cả các Widget
cần StatefulWidget để quản lý các trạng thái và kết nối với các
widget khác. Flutter widgets là một dạng reactive (kỹ
thuật reactive hay reactive programming là gì thì nó hơi khó giải
thích, các bạn tìm đọc trên mạng nhé ) gốc. Nó tương tự
như reactjs và StatefulWidget sẽ tự động thay đổi giao diện
hiển thị khi thay đổi trang thái. Sự thay đổi giao diện này được tối
ưu bằng cách tìm kiếm sự khác biệt giữa UI widget cũ và mới sau
đó chỉ vẽ lại những thay đổi cần thiết.
Lz?~??
Một khái niệm quan trọng của Flutter framework đó là các thành
phần sẽ được nhóm lại theo độ phức tạp và được sắp xếp rõ ràng
trong các tầng có độ phức tạp giảm dần. Một layer (tầng,lớp)
được tạo thành bằng việc sử dụng các class tiếp theo ngay canhtailieusharefree.blogspot.com

nó. Top của tất cả các layer là các widget đặc biệt
cho Android và iOS. Layer tiếp theo là widget gốc của flutter.
Tiếp lữa là Rendering layer, đây là level thấp nhất trong việc
sinh các thành phần của flutter app. Layer tiếp theo là nền tảng
gốc hệ điều hành.
Tổng quan về layer của Flutter được mô tả trong sơ đồ dưới đây:
Tổ?? ?ế?
Tổng kết những điểm chính về kiến trúc của Flutter tailieusharefree.blogspot.com

Trong Flutter, tất cả đều quy về các widget, một widget
phức hợp sẽ bao gồm các widget khác bên trong
Các tính năng tương tác sẽ đước tích hợp bất cứ khi nào
nhờ GestureDetector widget.
Trạng thái của các widget được quản lý cập nhật
bởi StatefulWidget widget.
Flutter cung cấp thiết kế class để bất kỳ lớp nào có thể
được lập trình tùy thuộc vào độ phức tạp của tác vụ.
tailieusharefree.blogspot.com

Bài 5: Giới thiệu ngôn ngữ Dart

Dart là một ngôn ngữ lập trình mã nguồn mở (open source) đa
năng (general purpose). Nó được phát triển bởi GoogleI. Dart là
một ngôn ngữ lập trình hướng đối tượng sử dụng cú pháp của C (
C-style syntax). Nó hỗ trợ các khái niệm như interface, class,...
không giốn như các ngôn ngữ lập tình khác, Dart không hỗ trợ
mảng (array). Dart collections có thể sử dụng các cấu trúc dữ liệu
(data structure) thay thế.
Đoạn code dưới đây minh hoạ một chương trình Dart cơ bản:
void main() {
print("Dart language is easy to learn");
}
B?ế? ?? Kể? }ữ ??ệ?
Biến (Variable) là tên đại diện cho nơi lữu trự dữ liệu còn Kiểu
dữ liệu (Data types) đơn giản là loại và kích thước của dữ liệu
liện kết với biến và hàm.
Dart sử dụng từ khoá var để khai báo biến.
var name = 'Dart';
Từ khoá final và const được sử dụng để khai báo hằng số
(constants). Như ví dụ dưới đây
void main() {
final a = 12;
const pi = 3.14;tailieusharefree.blogspot.com

print(a);
print(pi);
}
Dart hỗ trợ các kiểu dữ liệu dưới đây, chúng ta không cần thiết
phải khai báo kiểu dữ liệu cho biến
Numbers − Được sử dụng cho số – Integer và Double.
Strings − Được sử dụng cho chuỗi kí tự. Giá trị của
String được đặt trong dâu nháy đơn hoặc nháy kép.
Booleans − Được sử dụng cho giá trị Boolean đúng và
sai
Lists and Maps − Được sử dụng cho nhóm đối tượng.
Ví dụ một Danh sách đơn giản có thể được mô tả như
sau:
void main() {
var list = [1,2,3,4,5];
print(list);
}
Map có thể được mô tả như sau:
void main() {
var mapping = {'id': 1,'name':'Dart'};
print(mapping);
}
Dynamic − Trường hợp kiểu dữ liệu chưa được định
nghĩa thì giá trị mặc đinh là dynamic.
void main() {
dynamic name = "Dart";
print(name);tailieusharefree.blogspot.com

}
Đ?ề? ???ể? ?? ???? ?ặ?
Một khối điều khiển (decision making block) đánh giá một điều
kiện trước khi hướng dẫn thực thi. Dart hỗ trợ các khối lênh If,
If..else và switch.
Vòng lặp được sử dụng để lặp lại một khối lệnh cho đến khi một
điều kiện cụ thể được đáp ứng. Dart hỗ trợ các vòng lặp for..in,
while và do.. while
Ví dụ đoạn code hiển thị các số từ 1 đến 10
void main() {
for( var i = 1 ; i <= 10; i++ ) {
if(i%2==0) {
print(i);
}
}
}
H?? (F??|?????)
Hàm là một nhóm các câu lệnh nhằm thực hiện một tác vụ nhất
định. Chúng ta cùng xem một ví dụ về hàm trong Dart dưới đây:
void main() {
add(3,4);
}
void add(int a,int b) {
int c;tailieusharefree.blogspot.com

c = a+b;
print(c);
}
Hàm trên làm nhiệm vụ cộng hai tham số truyền vào, và in ra kết
quả trên màn hình
Lậ? ????? ?ướ?? ?ố? ?ượ??
Dart là một ngôn ngữ lập trình hướng đối tượng (object-oriented
language). Nó hỗ trợ một số tính năng của lập trình hướng đối
tượng như class, interface,...
Mỗi một class (lớp) định nghĩa cho một loại đối tượng. Một class
bao gồm những nội dung sau đây:
Các thuộc tính (Fields)
Các hàm Getter và setter
Hàm khởi tạo (Constructor)
Phương thức (Function)
Dưới đây là một minh hoạ các thành phần của một class
class Employee {
String name;

//getter method
String get emp_name {
return name;
}
//setter method
void set emp_name(String name) {
this.name = name;tailieusharefree.blogspot.com

}
//function definition
void result() {
print(name);
}
}
void main() {
//object creation
Employee emp = new Employee();
emp.name = "employee1";
emp.result(); //function call
}
Ở bài tiếp theo chúng ta sẽ tìm hiểu kĩ hơn về widget - thành
phần cơ bản nhất của Flutter
tailieusharefree.blogspot.com

Bài 6: Widget trong Flutter

Như đã nói trong các bài học trước widget là mọi thứ trong
Flutter (widgets are everything in Flutter framework). Đây là
thành phần cơ bản và chủ yếu nhất. Nó tương tự như là các view
ở trong Android.
Ở trong bài trước thì chúng ta đã tạo được các widget đơn giản,
trong bài học này chúng ta sẽ tìm hiểu kĩ hơn về cách tạo widget,
cách thức hoạt động và các loại widget được hỗ trợ bởi Fullter.
Trong ứng dụng Hello World, chúng ta đã tạo một widget tên
là MyHomePage
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);

final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title),
),
body: Center(child: Text( 'Hello
World',)),
);
}
}tailieusharefree.blogspot.com

Chúng ta sẽ tìm hiểu kỹ hơn về cách tạo widget này:
Đầu tiên chúng ta thấy rằng MyHomePage được kết thừa từ một
widget khác là StatelessWidget. Thông thường trong Flutter để
tạo một widget mới (widget của người dùng nhé, ko phải widget
mặc định) ta cần kế thừa một trong hai class là StatelessWidget
và StateFullWidget. Để hiểu hơn thì chúng ta sẽ nói rõ trong bài
Sate, nhưng chúng ta có thể hiêu đơn giản StatelessWidget là
một widget không có trang thái nào, nó chỉ nhận dữ liệu và hiển
thị một cách thụ động, không nhận bất cứ event nào.
StatelessWidget chỉ yêu cầu implement duy nhất một phương
thức build . Phương thức này lấy thông tin để dựng các widget
thông qua tham số BuildContext và trả về widget mà nó tạo ra.
Như đoạn code ở trên, ta có thể thấy hàm build sử dụng thuộc
tính title từ hàm khởi tạo để hiển thị têu đề cho ứng dụng. Còn
tham số Key được dùng để định dang cho widget.
Chúng ta thấy hàm build lại gọi tới một widget khác là Scaffold.
Widget này đóng vai trò như một phần nền để bố trí các thành
phần khác theo phong cách Material Design, tương tự
như DrawerLayout và CoordinatorLayout trong Android vậy.
Cuối cùng ta thấy có một widget là Center có nhiệm vụ bố
trí Text ở giữa màn hình.
Để hiểu hơn mối quan hệ giữa các widget chúng ta tham khảo sơ
đồ dưới đây:tailieusharefree.blogspot.com

Tổ?? ??z? ?ề |?| ??ạ? W?}?~? ????? F????~?
Trong Flutter tất các widget được phân loại dựa trên chức năng
thành 4 nhóm sau:
Các widget giao diện đặc thù theo từng nền tảng -
Platform widgets
Các widget hỗ trợ bố trí giao diện - Layout widgets
Các widget quản lý trạng thái - State maintenance
widgets
Các widget cơ bản độc lập với nền tảng - Platform
independent / basic widgets
Chúng ta sẽ tìm hiểu kĩ từng loại widget dưới đây:
P
Đây là các widget dành riêng cho từng nên tảng Android hay IOS
Các widget dành riêng cho Android được thiết kết theo Material
design guideline cho Android OS nên được gọi là Materialtailieusharefree.blogspot.com

widgets.
Các widget dành riêng cho iOS được thiết kế theo Human
Interface Guidelines bởi Apple và được gọi
là Cupertino widgets
Một số material widgets phổ biến nhất cho Android:
Scaffold
AppBar
BottomNavigationBar
TabBar
TabBarView
ListTile
RaisedButton
FloatingActionButton
FlatButton
IconButton
DropdownButton
PopupMenuButton
ButtonBar
TextField
Checkbox
Radio
Switch
Slider
Date & Time Pickers
SimpleDialog
AlertDialogtailieusharefree.blogspot.com

Một số Cupertino widgets phổ biến nhất cho IOS
CupertinoButton
CupertinoPicker
CupertinoDatePicker
CupertinoTimerPicker
CupertinoNavigationBar
CupertinoTabBar
CupertinoTabScaffold
CupertinoTabView
CupertinoTextField
CupertinoDialog
CupertinoDialogAction
CupertinoFullscreenDialogTransition
CupertinoPageScaffold
CupertinoPageTransition
CupertinoActionSheet
CupertinoActivityIndicator
CupertinoAlertDialog
CupertinoPopupSurface
CupertinoSlider
L
Trong Flutter, một widget có thể được tạo thành từ một hoặc
nhiều widget khác. Việc kết hợp nhiều widget thành một widget
được thực hiện thông qua các layout widget. Ví dụ, các widget
con có thể được căn giữa thông Center widget.tailieusharefree.blogspot.com

Một số layout widgets phổ biến:
Container − Một hình chữ nhật được thiết kế sử
dụng BoxDecoration widgets với background (nền),
border (đường viền) và shadow (bóng đổ).
Center − Căn giữa các widget con.
Row − Sắp xếp các widget con theo hàng ngang
(horizontal direction).
Column − Sắp xếp các widget con theo hàng dọc
(vertical direction).
Stack − Sắp xếp các widget con lên trên cùng
Chúng ta sẽ tim hiểu kỹ hơn trong bài Giới thiệu layout
widget ở các bài sau.
S
Trong Flutter, tất cả các widget đều kế thừa
từ StatelessWidget hoặc StatefulWidget.
Widget kế thừa từ StatelessWidget sẽ không có bất kì trạng thái
nào nhưng nó có thể bao gồm widget được kết thừa
từ StatefulWidget. Bản chất sự linh hoạt của ứng dụng là thông
qua hành vi tương tác của các widget và sự thay đổi trạng thái của
chúng. Ví dụ khi chạm vào một nút tăng giảm của bộ đếm, nó sẽ
làm tăng hoặc giảm trạng thái của bộ đếm trong widget và cơ
chế reactive nature (tự phản ứng) sẽ tự động thay đổi lại giao
diện của widget theo trạng thái mới.
Chúng ta sẽ tìm hiểu kỹ hơn về khái niệm StatefulWidget trong
bài học về State managementtailieusharefree.blogspot.com

P /
Flutter cung cấp một số lương lớn các widget cơ bản để tạo các
giao diện người dùng từ đơn giản đến phứ tạp độc lập với nền
tảng hệ điều hành. Chúng ta sẽ tìm hiểu một số widget cơ bản
dưới đây:
Text
Text widget được sử dụng để hiển thị một đoạn văn bản. Chúng
ta có thể định dạng văn bản thông qua thuộc
tính style vàTextStyle class. Ví dụ:
Text('Hello World!', style: TextStyle(fontWeight:
FontWeight.bold))
Text widget có một hàm constructor riêng, Text.rich, sử dụng
một TextSpan để mô tả các định dang. TextSpan widget có tính
chất đệ quy và nó có thể bao gồm các TextSpan khác. Ví dụ:
Text.rich(
TextSpan(
children: [
TextSpan(text: "Hello ", style:
TextStyle(fontStyle: FontStyle.italic)),
TextSpan(text: "World", style:
TextStyle(fontWeight: FontWeight.bold)),
],
),
)
Một số thuộc tính cơ bản của Text widget
maxLines, int − Số lượng dòng tối đatailieusharefree.blogspot.com

overflow, TextOverFlow − Xỷ lý việc tràn văn bản sử
dụng TextOverFlow class
style, TextStyle − Mô tả định dang văn bản
thông TextStyle class
textAlign, TextAlign − Căn lề văn bản: right, left,
justify,.. sử dụng TextAlign class
textDirection, TextDirection − Quy đinh chiều của
văn bản left-to-right hoặc right-to-left
Image
Image widget được sử dụng để hiển thị hình ảnh trong ứng
dụng. Image widget cung cấp các phương thức khởi tạo khác
nhau để load hình ảnh từ các nguồn khác nhau:
Image − Hình ảnh thông thường sử
dụng ImageProvider
Image.asset − Load hình ảnh từ flutter project’s assets
Image.file − Load hình ảnh từ system folder
Image.memory − Load hình ảnh từ memory
Image.Network − Load hình ảnh từ mạng network
Lựa chọn đơn giản nhất để hiển thị hình ảnh trong Flutter là đưa
hình ảnh vào thư mục assets của ứng dụng rồi load vào widget
khi cần:
Tạo một thư mục assets ở trong project và copy file ảnh
lưu vào.
Mô tả assets ở trong file pubspec.yaml như sau
flutter:
assets:tailieusharefree.blogspot.com

- assets/smiley.png
Nhớ đúng cú pháp trên nhé
Sau đó, load và hiển thị ảnh trong Ứng dụng bằng
widget.
Image.asset('assets/smiley.png')
Thay thế code MyHomePage widget trong ứng dụng
hello world như sau:
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( title: Text(this.title),
),
body: Center( child:
Image.asset("assets/smiley.png")),
);
}
}
Chạy thử ứng dụng hình ảnh sẽ được load lên như sau:tailieusharefree.blogspot.com

Một số thuộc tính cơ bản của Image widget:
image, ImageProvider − Kích thước ảnh
width, double − Độ rộng của ảnh
height, double − Độ cao của ảnh
alignment, AlignmentGeometry − Căn lề
Icon
Icon widget hiển thị hình ảnh các icon cơ bản
trong IconData class.
Icon(Icons.email)
Chúng ta sửa lại code MyHomePage widget như sau:
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);tailieusharefree.blogspot.com

final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title),),
body: Center( child: Icon(Icons.email)),
);
}
}
Chạy thử trên máy ảo
Bài viết khá dài, mình sẽ hướng dẫn các bạn chi tiết cách sử dụng
các widget chính để thiết kế giao diện ở những bài cuối nhé,tailieusharefree.blogspot.com

trước khi tìm hiểu kỹ các thành phần của Flutter

tailieusharefree.blogspot.com

Bài 7: Layout trong Flutter

Trong Flutter các layout cũng là một loại widget, nhiệm vụ của
chúng là bố trí các widget con, tạo nên giao diện người dùng cho
ứng dụng. Flutter cung cấp nhiều loại layout khác nhau
như Container, Center, Align... Chúng ta sẽ tìm hiểu chi tiết các
loại layout trong bài học này.
Có hai loại widget layout chính trong Flutter
Single Child Widgets - Chỉ có một widget con
Multiple Child Widgets - Có nhiều widget con
S????~ C???} W?}?~??
Các widget layout loại này chỉ có duy nhất một widget con và
thường có chức năng bố trí nhất định.
Ví dụ, Center widget chỉ căn giữa widget con so với widget cha
của nó và Container widget cung cấp khả năng linh hoạt trong
việc đặt widget con bên trong nó thông qua các tuỳ chọn như
padding, đường viền, nền,,,,
Single child widgets thích hợp cho việc tạo ra các widget có tính
ứng dụng cao và chỉ có một chức năng duy nhất như button,
label....
Chúng ta sẽ xem thử đoạn code tạo ra một custom button sử
dụng Container widget như sau:
class MyButton extends StatelessWidget {
MyButton({Key key}) : super(key: key);tailieusharefree.blogspot.com

@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0, color:
Color(0xFFFFFFFFFF)),
left: BorderSide(width: 1.0, color:
Color(0xFFFFFFFFFF)),
right: BorderSide(width: 1.0,
color: Color(0xFFFF000000)),
bottom: BorderSide(width: 1.0,
color: Color(0xFFFF000000)),
),
),
child: Container(
padding: const
EdgeInsets.symmetric(horizontal: 20.0,
vertical: 2.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0,
color: Color(0xFFFFDFDFDF)),
left: BorderSide(width: 1.0,
color: Color(0xFFFFDFDFDF)),
right: BorderSide(width: 1.0,
color: Color(0xFFFF7F7F7F)),tailieusharefree.blogspot.com

bottom: BorderSide(width: 1.0,
color: Color(0xFFFF7F7F7F)),
),
color: Colors.grey,
),
child: const Text(
'OK',textAlign: TextAlign.center,
style: TextStyle(color: Colors.black)
),
),
);
}
}
Đoạn code trên sử dụng 2 widget một Container widget và
một Text widget. Kết quả như sau:
Một số single child layout widgets quan trọng trong Flutter
Padding − Được sử dụng để padding child widget. Ở
đây, padding có thể sử dụng EdgeInsets class.
Align − Căn lề child widget sử dụng thuộc
tính alignment. Giá trị của alignment có thể được
cung cấp
bởi FractionalOffset class. FractionalOffset class
xác định vị trí của phần tử từ vị trí điểm trên cùng bên
trái
Một số ví dụ về aligntailieusharefree.blogspot.com

FractionalOffset(1.0, 0.0) biểu thị phiá trên bên phải
FractionalOffset(0.0, 1.0) biểu thị phía dưới bên trái
Ví dụ qua đoạn code sau:
Center(
child: Container(
height: 100.0,
width: 100.0,
color: Colors.yellow, child: Align(
alignment: FractionalOffset(0.2, 0.6),
child: Container( height: 40.0, width:
40.0, color: Colors.red,
),
),
),
)
Một số single child layout khác:
FittedBox
AspectRatio
ConstrainedBox
Baseline
FractinallySizedBox
IntrinsicHeight
IntrinsicWidth
LiimitedBox
OffStage
OverflowBox
SizedBoxtailieusharefree.blogspot.com

SizedOverflowBox
Transform
CustomSingleChildLayout
Mình sẽ sửa lại một chút code MyHomePage trong ứng
dụng Hello Word ở bài trước để các bạn hiểu rõ hơn
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello World Demo Application',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
@override
Widget build(BuildContext context) {tailieusharefree.blogspot.com

return Container(
decoration: BoxDecoration(color:
Colors.orange,),
padding: EdgeInsets.all(25), child:
Center(
child:Text(
'Hello World', style: TextStyle(
color: Colors.red, letterSpacing: 0.5,
fontSize: 30,
),
textDirection: TextDirection.ltr,
),
)
);
}
}
Chạy thử trên máy ảotailieusharefree.blogspot.com

M??????~ C???} W?}?~??
Loại widget layout này sẽ cho phép có nhiều hơn một widget
con. Ví dụ Row widget cho phép bố trí các widget con theo chiều
ngang thành một hàng trong khi Column widget cho phép bố trí
các widget con theo chiều dọc thành một cột.
Một số widget layout dạng này được sử dụng phổ biến
Row
Column
ListView
GridView
Expanded tailieusharefree.blogspot.com

Table
Flow
Stack
V? }ụ
Để hiểu hơn về layout trong Flutter mình sẽ hướng dẫn các bạn
thực hiện một giao diện người dùng phức hợp gọi là product
listing với thiết kế tuỳ chỉnh sử dụng cả single và multiple child
layout widget
Các bạn tạo project Flutter mới đặt tên là product_layout_app.
Thay thế main.dart bằng đoạn code sau đây:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(
primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo
home page'),
);
}
}
class MyHomePage extends StatelessWidget {tailieusharefree.blogspot.com

MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title),),
body: Center(child: Text( 'Hello World', )),
);
}
}
Chúng ta tạo một widget là MyHomePage kế thừa
từ StatelessWidget
Tiếp theo chúng ta sẽ tạo một widget ProductBox để hiển thị
thông tin sản phẩm, bao gồm hình ảnh, tên sản phẩm, mô tả, và
giá bán như thiết kế dưới đây:
Code của ProductBox như sau:
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name,
this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;tailieusharefree.blogspot.com

final int price;
final String image;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2), height: 120,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly, children: <Widget>[
Image.asset("assets/appimages/"
+image), Expanded(
child: Container(
padding:
EdgeInsets.all(5), child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[

Text(this.name,
style: TextStyle(fontWeight:

FontWeight.bold)), Text(this.description),
Text("Price: " +
this.price.toString()),
],
)
)tailieusharefree.blogspot.com

)
]
)
)
);
}
}
Giải thích:
ProductBox có 4 thuộc tính như sau:
name - Tên sản phẩm
description - Mô tả sản phẩm
price - Giá của sản phẩm
image - Hình anhr cửa sản phẩm
ProductBox sử dụng 7 widget như sau:
Container
Expanded
Row
Column
Card
Text
Image
Để hiểu hơn cấu trúc của ProductBox widget ta xem sơ đồ cấu
trúc sau:tailieusharefree.blogspot.com

Bây giờ, chúng ta thêm ảnh của sản phẩm vào thư mục assets của
ứng dụng, tạo thư mục con appimages để chứa ảnh và cấu hình
assets trong file pubspec.yaml như sau:
assets:
- assets/appimages/floppydisk.jpg
- assets/appimages/iphone.jpg
- assets/appimages/laptop.jpg
- assets/appimages/pendrive.jpg
- assets/appimages/pixel.jpg
- assets/appimages/tablet.jpgtailieusharefree.blogspot.com

Các bạn nhấn chuột phải vào ảnh rồi download về máy nhé.tailieusharefree.blogspot.com

Bây giờ để hiển thị nhiều sản phẩm ta sử dụng ListView
widget để chứa các ProductBox, sửa lại
code MyHomePage như sau:
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text("Product
Listing")),
body: ListView(
shrinkWrap: true, padding: const
EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget> [
ProductBox(
name: "iPhone",
description: "iPhone is the
stylist phone ever",
price: 1000,
image: "iphone.jpg"
),
ProductBox(
name: "Pixel",
description: "Pixel is the most
featureful phone ever",tailieusharefree.blogspot.com

price: 800,
image: "pixel.jpg"
),
ProductBox(
name: "Laptop",
description: "Laptop is most
productive development tool",
price: 2000,
image: "laptop.jpg"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most
useful device ever for meeting",
price: 1500,
image: "tablet.jpg"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful
storage medium",
price: 100,
image: "pendrive.jpg"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is
useful rescue storage medium",tailieusharefree.blogspot.com

price: 20,
image: "floppydisk.jpg"
),
],
)
);
}
}
Full code (main.dart) của ứng
dụng (product_layout_app) như sau:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(
primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo
home page'),
);
}
}

class MyHomePage extends StatelessWidget {tailieusharefree.blogspot.com

MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text("Product
Listing")),
body: ListView(
shrinkWrap: true, padding: const
EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget> [
ProductBox(
name: "iPhone",
description: "iPhone is the
stylist phone ever",
price: 1000,
image: "iphone.jpg"
),
ProductBox(
name: "Pixel",
description: "Pixel is the most
featureful phone ever",
price: 800,
image: "pixel.jpg"
),
ProductBox(tailieusharefree.blogspot.com

name: "Laptop",
description: "Laptop is most
productive development tool",
price: 2000,
image: "laptop.jpg"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most
useful device ever for meeting",
price: 1500,
image: "tablet.jpg"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful
storage medium",
price: 100,
image: "pendrive.jpg"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is
useful rescue storage medium",
price: 20,
image: "floppydisk.jpg"
),
],tailieusharefree.blogspot.com

)
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name,
this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2), height: 120,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly, children: <Widget>[
Image.asset("assets/appimages/" +image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[tailieusharefree.blogspot.com

Text(this.name, style:
TextStyle(fontWeight:
FontWeight.bold)),
Text(this.description),
Text("Price: " +
this.price.toString()),
],
)
)
)
]
)
)
);
}
}
Chạy thử ứng dụng trên máy ảo:tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

Bài 8: Gesture trong Flutter

Gesture (cử chỉ) là cách mà người dùng tương tác với các thiết bị
di động. Có rất nhiều cử chỉ khác nhau như vuốt, chạm,
lắc... Gesture trong Flutter giúp ta xử lý các cử chỉ của người
dùng, tương tự như việc bắt sự kiện trong Android.
Một số cử chỉ được sử dụng rộng rãi:
Tap − Chạm vào bề mặt thiết bị bằng đầu ngón tay
trong thời gian ngắn sau đỏ thả ngón tay ra ngay
Double Tap − Tap 2 lần trong thời gian ngắn
Drag − Chạm vào bề mặt của thiết bị bằng đầu ngón
tay và sau đó di chuyển đầu ngón tay một cách ổn định
và cuối cùng thả ngón tay ra.
Flick − Tương tự như drag nhưng thực hiện nhanh hơn.
Pinch − Chụm bề mặt của thiết bị bằng hai ngón tay
Spread/Zoom − Ngược lại với Pinch.
Panning − Chạm vào bề mặt của thiết bị bằng đầu
ngón tay và di chuyển nó theo bất kỳ hướng nào mà
không nhả đầu ngón tay.
Flutter cung cấp một sự hỗ trợ tuyết vời để xử lý tất cả các loại cử
chỉ thông qua một tiện ích duy nhất GestureDetector. Để xác
định các cử chỉ tác động lên một widget, ta chỉ cần đặt widget đó
bên trong GestureDetector widget. GestureDetector sẽ bắt các cử
chỉ và gửi nhiều sự kiện dựa trên cử chỉ đó.tailieusharefree.blogspot.com

Một số cử chỉ và các sự kiện tương ứng được đưa ra dưới đây
Tap
onTapDown
onTapUp
onTap
onTapCancel
Double tap
onDoubleTap
Long press
onLongPress
Vertical drag
onVerticalDragStart
onVerticalDragUpdate
onVerticalDragEnd
Horizontal drag
onHorizontalDragStart
onHorizontalDragUpdate
onHorizontalDragEnd
Pan
onPanStart
onPanUpdate
onPanEnd
Bây giờ chúng ta sẽ mở lại ứng dụng Hello world và thêm vào
việc xử lý cử chỉ bằng việc sửa lại MyHomePage widget như
sau:
body: Center(tailieusharefree.blogspot.com

child: GestureDetector(
onTap: () {
_showDialog(context);
},
child: Text( 'Hello World', )
)
),
Nhìn vào đoạn code trên các bạn có thể thấy widget Text được
đặt trong GestureDetector widget, để bắt sự kiện onTap và hiển
thị một Dialog khi ta chạm vào Text:
Ở hàm _showDialog ta sẽ gọi một widget là AlertDialog để
hiển thị một thông báo, sử dụng đoạn code như sau:
// user defined function void
_showDialog(BuildContext context) {
// flutter defined function
showDialog(
context: context, builder: (BuildContext
context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Message"),
content: new Text("Hello World"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
}, tailieusharefree.blogspot.com

),
],
);
},
);
}
Toàn bộ code của (main.dart) sau khi chỉnh sửa
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello World Demo Application',
theme: ThemeData( primarySwatch:
Colors.blue,),
home: MyHomePage(title: 'Home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

// user defined functiontailieusharefree.blogspot.com

void _showDialog(BuildContext context) {
// user defined function void
_showDialog(BuildContext context) {
// flutter defined function
showDialog(
context: context, builder: (BuildContext
context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Message"),
content: new Text("Hello World"),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title),),tailieusharefree.blogspot.com

body: Center(
child: GestureDetector(
onTap: () {
_showDialog(context);
},
child: Text( 'Hello World', )
)
),
);
}
}
Chạy thử trên máy ảo, chạm vào chữ Hello Word sẽ có một cửa
sổ dialog hiện ra như sau:tailieusharefree.blogspot.com

Cuối cùng, Flutter cũng cung cấp một cơ chế phát hiện cử chỉ
cấp thấp thông qua Listener widget. Nó sẽ phát hiện tất cả các
tương tác của người dùng và sau đó gửi các sự kiện sau:
PointerDownEvent
PointerMoveEvent
PointerUpEvent
PointerCancelEvent
Flutter còn cung cấp một số nhỏ các widget để thực hiện các cử
chỉ cụ thể đơn giản cũng như phức tạp:
Dismissible − Hỗ trợ flick gesture để đóng widget.
Draggable − Hỗ trợ drag gesture để di chuyển widget.
LongPressDraggable − Hỗ trợ drag gesture để duy
chuyển widget, khi widget cha có thể kéo thả
DragTarget − Chấp nhận Draggable widget
IgnorePointer − Ẩn widget
AbsorbPointer − Dừng việc xử lý cử chỉ trên widget
Scrollable − Hỗ trợ cuộn nội dung trong widget
tailieusharefree.blogspot.com

Bài 9: Quản lý trạng thái Sate trong
Flutter
Quản lý sate (trạng thái) của ứng dụng là một trong những bước
quan trọng và cần thiết trong vòng đời của một ứng dụng.
Chúng ta cùng xem xét một ứng dụng shopping cart (giỏ hàng)
đơn giản dưới đây:
Người dùng đăng nhập bằng thông tin của họ vào trong
ứng dụng.
Khi người dùng đăng nhập, ứng dụng sẽ hiển thị thông
tin người dùng ở tất cả các màn hình
Một lần nữa, khi người dùng chọn một sản phẩm và lưu
vào trong giỏ hàng (cart). Thông tin giỏ hàng sẽ tồn tại
ở tất cả các trang cho đến khi người dùng xem giỏ hàng
Thông tin người dùng và giỏ hàng tồn tại ở bất kì
trường hợp nào gọi là trạng thái của ứng dụng ở thời
điểm đó
Việc quản lý trạng thái có thể được chia làm hai loại dựa vào thời
gian tồn tại của trạng thái đó trong ứng dụng:
Ephemeral (ngắn hạn)− Kéo dài trong vài giây như
trạng thái của hiệu ứng (animation) hoặc một trang đơn
như trang thông tin đánh gía sản phẩm. Flutter hỗ trợ
quản lý trạng thái loại này thông qua StatefulWidget.
App state (trạng thái ứng dụng) − Kèo dài trong toàn
bộ ứng dụng như thông tin người dùng, thông tin giỏtailieusharefree.blogspot.com

hàng... Flutter hỗ trợ quản lý trạng thái loại này thông
qua scoped_model
tailieusharefree.blogspot.com

Bài 10: Statefulwidget trong Flutter

Trong bài trước mình đã giới thiệu có hai phương thức quản lý
trạng thái trong Flutter đó là quản lý trạng thái ngắn hạn và quản
lý trạng thái ứng dụng. Trong bài này chúng ta sẽ tìm hiểu quản
lý trạng thái ngắn hạn với StatefulwidgetTrong bài trước mình đã
giới thiệu có hai phương thức quản lý trạng thái trong Flutter đó
là quản lý trạng thái ngắn hạn và quản lý trạng thái ứng dụng.
Trong bài này chúng ta sẽ tìm hiểu quản lý trạng thái ngắn hạn
với Statefulwidget
Vì ứng dụng Flutter được tạo nên từ các widget, do đo sviệc quản
lý trạng thái cũng được thực hiện bởi widget. Mấu chốt của việc
quản lý trạng thái là Statefulwidget. Widget được kết thừa từ
Statefulwidget để duy trì trạng thái và quản lý các trạng thái con
của nó.
Statefulwidget cung cấp tuỳ chọn cho widget để tạo ra các trạng
thái, việc khởi tạo trang thái ban đầu của widget được thực hiện
qua hàm createState và hàm setState dùng để thay đổi trạng thái
khi cần. Sự thay đổi trạng thái được thực hiện qua các gesture (cử
chỉ). Ví dụ, sự đánh giá (rating) của một sản phẩm có thể được
thay đổi khi người dùng chạm vào sao của rating widget.
Bây giờ chúng ta sẽ tạo một widget là RatingBox để làm quen với
quản lý trạng thái. Mục đích của widget là hiển thị số lượng đánh
giá hiện tại của sản phẩm.tailieusharefree.blogspot.com

Bước đầu tiên chúng ta tạo một widget là RatingBox kế thừa từ
StatefulWidget.
class RatingBox extends StatefulWidget { }
Tạo một state cho RatingBox là _RatingBoxState kế thừa từ
State<T>
class _RatingBoxState extends State<RatingBox> { }
Ghi đè phương thức createState của StatefulWidget để tạo trang
thái cho widget bằng _RatingBoxState
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() =>
_RatingBoxState();
}
Tạo giao diện người dùng cho RatingBox widget bằng phương
thức build của _RatingBoxState. Thông thường chúng ta sẽ tạo
giao diện bằng phương thức build của RatingBox widget. Tuy
nhiên khi cần quản lý trạng thái chúng ta cần tạo giao diện ở
trong _RatingBoxState widget. Điều này đảm bảo cho việc hiển
thị lại giao diện người dùng khi trạng thái của widget thay đổi.
Widget build(BuildContext context) {
double _size = 20;
print(_rating);

return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,tailieusharefree.blogspot.com

children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
iconSize: _size,
),
), Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
iconSize: _size,
),
), Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),tailieusharefree.blogspot.com

color: Colors.red[500],
iconSize: _size,
),
),
],
);
}
Ở đây, chúng ta sử dụng 3 ngôi sao, được tạo nên từ IconButton
widget và sắp xếp sử dụng Row widget trên cùng 1 hàng ngang.
Ý tưởng ở đây là hiển thị rating thông qua dãy các ngôi sao. Ví
dụ nếu rating của sản phẩm là 2 sao thì hai ngôi sao đầu sẽ có
màu đỏ và ngôi sao cuối cùng sẽ có màu trắng.
Thêm phương thức thay đổi trạng thái cho _RatingBoxState
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}tailieusharefree.blogspot.com

Thay đổi trạng thái của rating theo cử chỉ của người dùng (chạm
vào ngôi sao)
Widget build(BuildContext context) {
double _size = 20;
print(_rating);

return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ?
Icon(Icons.star, size: _size,) :tailieusharefree.blogspot.com

Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
Ở đây sự kiện onPressed sẽ gọi hàm tương ứng để thay đổi trạng
thái của widget và hiển thị ra giao diện. Khi trạng thái bị thay đổi,
hàm buid sẽ được gọi lại và giao diện sẽ được hiển thị lại.
Toàn bộ code của RatingBox widget như sau:
class RatingBox extends StatefulWidget {
@overridetailieusharefree.blogspot.com

_RatingBoxState createState() =>
_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {
setState( () {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,tailieusharefree.blogspot.com

children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 2 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(tailieusharefree.blogspot.com

icon: (_rating >= 3 ?
Icon(Icons.star, size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Bây giờ ta sẽ chỉnh sửa lại ứng dụng product_layout_app ở bài
trước bằng cách bổ sung RatingBox widget và thêm vào
ProductBox như sau:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(
primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo
home page'),tailieusharefree.blogspot.com

);
}
}

class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text("Product
Listing")),
body: ListView(
shrinkWrap: true, padding: const
EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget> [
ProductBox(
name: "iPhone",
description: "iPhone is the
stylist phone ever",
price: 1000,
image: "iphone.jpg"
),
ProductBox(
name: "Pixel",tailieusharefree.blogspot.com

description: "Pixel is the most
featureful phone ever",
price: 800,
image: "pixel.jpg"
),
ProductBox(
name: "Laptop",
description: "Laptop is most
productive development tool",
price: 2000,
image: "laptop.jpg"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most
useful device ever for meeting",
price: 1500,
image: "tablet.jpg"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful
storage medium",
price: 100,
image: "pendrive.jpg"
),
ProductBox(
name: "Floppy Drive",tailieusharefree.blogspot.com

description: "Floppy drive is
useful rescue storage medium",
price: 20,
image: "floppydisk.jpg"
),
],
)
);
}
}
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() =>
_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState( () {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState( () {
_rating = 2;
});
}
void _setRatingAsThree() {tailieusharefree.blogspot.com

setState( () {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 1 ? Icon(Icons.star,
size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(tailieusharefree.blogspot.com

icon: (_rating >= 2 ? Icon(Icons.star,
size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_rating >= 3 ? Icon(Icons.star,
size: _size,) :
Icon(Icons.star_border, size:
_size,)),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name,
this.description, this.price, this.image}) :tailieusharefree.blogspot.com

super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/"
+ image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,
style: TextStyle(fontWeight: FontWeight.bold)),

Text(this.description),tailieusharefree.blogspot.com

Text("Price: " +
this.price.toString()),
RatingBox(),
],
)
)
)
]
)
)
);
}
}
Các bạn chạy thử ứng dụng, và chạm vào các ngôi sao để xem
trạng thái của RatingBox thay đổitailieusharefree.blogspot.com

Như vậy trong bài này mình đã hướng dẫn các bạn làm quen với
việc quản lý trạng thái của widget thông qua StatefulWidget.
Chúc các bạn học tốt
tailieusharefree.blogspot.com

Bài 11: ScopedModel trong Flutter

Trong bài trước chúng ta đã tìm hiểu cách quản lý trạng thái của
widget bằng SatefullWidget, trong bài hôm nay chúng ta tìm
hiểu cách quản lý trạng thái trong ứng dụng bằng ScopedModel
Flutter cung cấp một phương pháp đơn giản để quản lý trạng thái
của ứng dụng sử dụng scoped_model package. Flutter package
đơn giản là một thư viện với những phương thức được sử dụng
nhiều lần. Chúng ta sẽ tìm hiểu kỹ về package trong Flutter ở các
bài học tới.
scoped_model cung cấp 3 class chính cho phép quản lý trạng
thái của ứng dụng một cách mạnh mẽ:sdf
M
Model đóng gói trạng thái của một ứng dụng. Chúng ta có thể sử
dụng nhiều Model (bằng việc kế thừa Model class) để quản lý
trạng thái của ứng dụng. Model có một phương thức duy nhất
là notifyListeners, nó được gọi bất cứ khi nào trạng thái của
Model thay đổi. notifyListeners sẽ thực hiện các công việc cần
thiết để cập nhật giao diện.
Ví dụ ta tạo một model là Product
class Product extends Model {
final String name;
final String description;
final int price;tailieusharefree.blogspot.com

final String image;
int rating;

Product(this.name, this.description,
this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic>
json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating; notifyListeners();
}
}
SM
ScopedModel là một widget, chúng ta hiểu đơn giản nó là một
tiện ích để chúng ta có thể dễ dàng chuyển Data Model từ widget
cha xuống các widget con, cháu của nó. Ngoài ra nó còn có
nhiệm vụ rebuild lại các widget con giữ các model mà trong
trường hợp model này được cập nhật. Nếu cần nhiều hơn một
Data Model thì chúng ta có thể sử dụng lồng ScopeModel. Dưới
đây là hai dạng ScopedModel :tailieusharefree.blogspot.com

Single model :
ScopedModel<Product>(
model: item, child: AnyWidget()
)
Multiple model
ScopedModel<Product>(
model: item1,
child: ScopedModel<Product>(
model: item2, child: AnyWidget(),
),
)
ScopeModel.of là một phương thức dùng để lấy Data Model dưới
ScopeModel. Và nó có thể được sử dụng khi Data Model thay đổi
kể cá khi giao diện (UI) không thay đổi. Dưới đây là ví dụ khi ta
thay đổi UI( đánh giá ) của một sản phẩm
ScopedModel.of<Product>(context).updateRating(2);
ScopedModelDescendant
ScopedModelDescendant là một widget, nó lấy Data Model từ lớp
cha và build lại UI bất kí khi nào Data Model thay đổi.
ScopedModelDescendant có 2 thuộc tính là builder và child.
Child là phần UI không bị thay đổi và sẽ được chuyển cho hàm
builder . Hàm buider sẽ nhận 3 đối số:
1. Content :ScopedModelDescendant chuyển sang context
của ứng dụng
2. Child : Một phần của UI và không thay đổi dựa trên
Data Model
3. Model : Dưới đây là ví dụ tường minh tailieusharefree.blogspot.com

return ScopedModelDescendant<ProductModel>(
builder: (context, child, cart) => { ... Actual
UI ... },
child: PartOfTheUI(),
);
Bây giờ chúng ta sẽ sử dụng các ví dụ từ các bài học trước và sử
dụng ScopeModel thay vì StatefulWidget :
1. Tạo một ứng dụng Flutter mới với tên project tùy ý
bạn, ở đây tôi sẽ sử dụng tên
là product_scoped_model_app
2. Sau đó thay thế các dòng code mặc định trong hàm
main.dart bằng product_state_app code nhé
3. Coppy các hình ảnh từ assets mà đã sử dụng trong các
bài trước, tôi sẽ để link lại đây nhé
( https://vncoder.vn/bai-hoc/layout-trong-flutter-225 )
sau đó vào pubspec.yaml file tìm đến mục assets và dán
vào
flutter:

assets:
- assets/floppy.jpg
- assets/iphone.jpg
- assets/laptop.jpg
- assets/pendrive.jpg
- assets/pixel.jpg
- assets/tablet.jpgtailieusharefree.blogspot.com

Bây giờ chúng ta phải sử dụng gói thư viện của bên thứ ba vì nó
không có trong Framework của flutter
Bằng cách thêm Scope_model
vào pubspec.yaml ởphần dependencies
dependencies: scoped_model: ^1.0.1
Oke, bạn nên sử dụng version mới nhất nhé, cập nhật tại đây
: https://pub.dev/packages/scoped_model
Bây giờ bạn hãy thay thế đoạn code mặc định(main.dart) bằng
đoạn code của chúng tôi nhé
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch:
Colors.blue,),
home: MyHomePage(title: 'Product state
demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);tailieusharefree.blogspot.com

final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Text( 'Hello World', )
),
);
}
}
Bạn nhớ import Scope_model vào main.dart nhé ^^
import 'package:scoped_model/scoped_model.dart';
Giờ chúng ta sẽ tạo lớp Product quen thuộc, Product.dart là lớp
chứa thông tin của sản phẩm Product

import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
tailieusharefree.blogspot.com

Product(this.name, this.description,
this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic>
json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating;
notifyListeners();
}
}
Tốt rồi, giờ chúng ta sẽ sử dụng thuộc tính notifyListeners để
lắng nghe sự thay đổi của UI khi mà người dùng đánh giá
Chúng ta sẽ viết phương thức getProduct để tạo ra nội dung cho
lớp Product
import product.dart in main.dart
import 'Product.dart';
static List<Product> getProducts() {
List<Product> items = <Product>[];

items.add(
Product(tailieusharefree.blogspot.com

"Pixel",
"Pixel is the most feature-full phone
ever", 800,
"pixel.jpg", 0
)
);
items.add(
Product(
"Laptop", "Laptop is most productive
development tool", 2000,
"laptop.jpg", 0
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever
for meeting", 1500,
"tablet.jpg", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100, "pendrive.jpg", 0
)
);tailieusharefree.blogspot.com

items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage
medium", 20,
"floppy.jpg", 0
)
);
return items;
}
OK, tiếp tục chúng ta sẽ tạo một widget mới có tên là RatingBox
và sử dụng Scope_model để support nhé
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key:
key);
final Product item;

Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),tailieusharefree.blogspot.com

child: IconButton(
icon: (
item.rating >= 1
? Icon( Icons.star, size:
_size, )
: Icon( Icons.star_border,
size: _size, )
), color: Colors.red[500],
onPressed: () =>
this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
) : Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () =>
this.item.updateRating(2),tailieusharefree.blogspot.com

iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () =>
this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
ở đây chúng ta sử dụng StatelessWidget thay
vì StatefulWidget.Cũng như vậy, chúng ta đã sử dụng phươngtailieusharefree.blogspot.com

thức Product model’s updateRating để set giá trị phần đánh giá
tiếp theo chúng ta sẻ điều chỉnh widget ProductBox để làm việc
với lớp Product, ScopedModel và ScopedModelDescendant
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key:
key);
final Product item;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" +
this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child:
ScopedModel<Product>(
model: this.item,
child: Column(tailieusharefree.blogspot.com

mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[

Text(this.item.name,
style:
TextStyle(fontWeight: FontWeight.bold)),

Text(this.item.description),
Text("Price: "
+

this.item.price.toString()),

ScopedModelDescendant<Product>(
builder:
(context, child, item)
{ return
RatingBox(item: item); }
)
],
)
)
)
)
]
),
)tailieusharefree.blogspot.com

);
}
}
Các bạn có thể thấy chúng ta đã đóng gói widget RatingBox bên
trong ScopedModel và ScopedModelDecendant
Bây giờ chúng ta sẽ thay đổi widget MyHomePage để sử dụng
widget ProductBox như sau
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
final items = Product.getProducts();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item:
items[index]);
},
)
);
}
}tailieusharefree.blogspot.com

Ở đây chúng ta đã sử dụng ListView.builder để xây dựng động
danh sách Product
Vậy là xong, dưới đây là toàn bộ code được sử dụng :
Product.dart
import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;

Product(this.name, this.description,
this.price, this.image, this.rating);
factory Product.fromMap(Map<String, dynamic>
json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);n
} void cn "Laptop is most productive
development tool", 2000, "laptop.jpg", 0));
items.add(
Product(
"Tablet"cnvn,tailieusharefree.blogspot.com

"Tablet is the most useful device ever
for meeting", 1500,
"tablet.jpg", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium", 100,
"pendrive.jpg", 0
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage
medium", 20,
"floppy.jpg", 0
)
)
; return items;
}
main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'Product.dart';

void main() => runApp(MyApp());tailieusharefree.blogspot.com

class MyApp extends StatelessWidget {
// This widget is the root of your application

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state
demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
final items = Product.getProducts();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: ListView.builder(
itemCount: items.length, tailieusharefree.blogspot.com

itemBuilder: (context, index) {
return ProductBox(item:
items[index]);
},
)
);
}
}
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key:
key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 1? Icon(
Icons.star, size: _size, )tailieusharefree.blogspot.com

: Icon( Icons.star_border,
size: _size, )
),
color: Colors.red[500],
onPressed: () =>
this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () =>
this.item.updateRating(2),
iconSize: _size,
),
), tailieusharefree.blogspot.com

Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3 ?
Icon( Icons.star, size:
_size, )
: Icon( Icons.star_border,
size: _size, )
),
color: Colors.red[500],
onPressed: () =>
this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key:
key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,tailieusharefree.blogspot.com

child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" +
this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child:
ScopedModel<Product>(
model: this.item,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(

this.item.name, style: TextStyle(
fontWeight:
FontWeight.bold
)
),

Text(this.item.description),tailieusharefree.blogspot.com

Text("Price: " +
this.item.price.toString()),

ScopedModelDescendant<Product>(
builder:
(context, child, item) {
return
RatingBox(item: item);
}
)
],
)
)
)
)
]
),
)
);
}
}
Các bạn hãy thử chạy và xem kết quả. Nó cơ bản hoạt động
giống ví dụ trước nhưng ở đây có sử dụng thêm khái niệm
Scope_model.
Scope_model - chúng ta có thể hiểu đơn giản nó là một
framework , ví dụ khi bạn code "thô", lúc data model thay đổi, ở
hàm setState() thay vì rebuild lại tất cả các widget (Btn, txt, ..) thì
sẽ phạm phải quy tắc Single Responsibility thì Scope_modeltailieusharefree.blogspot.com

được sinh ra với mục đích chỉ rebuild data model bị thay đỏi thay
vì rebuild tất cả widget
tailieusharefree.blogspot.com

Bài 12: Navigator và Routing


Như chúng ta đã biết, trong Flutter những gì chúng ta nhìn thấy
được gọi là các widget. Navigator cũng là một widget có chức
năng quản lý các trang của ứng dụng theo định dạng giống như
ngăn xếp. Trong bất kì ứng dụng nào, việc điều hướng từ một
Full-screen ( page/screen ) để làm một công việc xác định nào đó
( chuyển sang một Full-screen khác) sử dụng Navigator widget
thì được gọi là Routing. Flutter cung cấp cho chúng ta lớp
Routing cơ bản - MaterialPageRoute với hai phương thức
- Navigator.push và Navigator.pop . Hôm nay chúng ta hãy
cùng nhau tìm hiểu kĩ về chức năng không thể thiểu trong lập
trình Flutter này nhé !
MaterialPageRoute
Đây là một widget được sử dụng để render giao diện người dùng
nhằm thay thế toàn bộ màn hình với một hiệu ứng chuyển đặc
biệt nào đó
Tại đây, hàm buider sẽ chấp nhận chức năng để xây dựng nội
dung bằng cách thay thế context hiện tại của ứng dụng
MaterialPageRoute(builder: (context) => Widget())
Navigation.push
Từ một màn hình bất kì, ta muốn chuyển sang một màn hình
khác sử dụng MaterialPageRoute widget như sau :tailieusharefree.blogspot.com

Navigator.push( context,
MaterialPageRoute(builder: (context) => Widget()),
);
Navigation.pop
Được sử dụng để quay về trang trước, các sử dụng đơn giản như
sau :
Navigator.pop(context);
Để hiểu sâu hơn về Navigator chúng ta sẽ bắt tay vào xây dựng
một ứng dụng đơn giản;. Let get started !!!
Tạo một ứng dụng Flutte mới với tên bất kì bạn muốn.Hãy copy
thư mục assets từ product_nav_app sang product_state_app và
thêm assets vào file pubspec.yaml.( Hoặc các bạn có thể qua bài
học này để lấy hình ảnh nhé https://vncoder.vn/bai-hoc/layout-
trong-flutter-225)
assets:
- assets/floppydisk.jpg
- assets/iphone.jpg
- assets/laptop.jpg
- assets/pendrive.jpg
- assets/pixel.jpg
- assets/tablet.jpg
Thay thế hàm main.dart mặc định bằng hàm main dưới đây
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.tailieusharefree.blogspot.com

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: 'Product state demo home page'
),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Text('Hello World',)
),
);
} tailieusharefree.blogspot.com

}
Tiếp theo chúng ta sẽ tạo một lớp Product như sau :
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description,
this.price, this.image);
}
Tiếp đến chúng ta sẽ tạo phương thức getProduct từ lớp Product
import product.dart in main.dart
import 'Product.dart
static List<Product> getProducts() {
List<Product> items = <Product>[];

items.add(
Product(
"Pixel",
"Pixel is the most feature-full phone
ever", 800,
"pixel.png"
)
);
items.add(
Product(
"Laptop",tailieusharefree.blogspot.com

"Laptop is most productive development
tool",
2000, "
laptop.png"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever
for meeting",
1500,
"tablet.png"
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png"
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage
medium",tailieusharefree.blogspot.com

20,
"floppy.png"
)
);
return items;
}
';
Bây giờ chúng ta sẽ tạo một widget mới có tên là RatingBox dùng
để đánh giá sản phẩm.
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState()
=>_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {tailieusharefree.blogspot.com

_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,tailieusharefree.blogspot.com

iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(tailieusharefree.blogspot.com

Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Chúng ta sẽ tạo một ProductBox widget là một item trong list
Product dùng để hiển thị thông tin và đánh giá sản phẩm.
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key:
key);
final Product item;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,tailieusharefree.blogspot.com

child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/"
+ this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:
TextStyle(fontWeight: FontWeight.bold)),

Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)
)
)
]tailieusharefree.blogspot.com

),
)
);
}
}
Tiếp tục, ta sẽ viết hàm có tên là MyHomePage widget để hiển thị
toàn bộ danh sách Product và ta sẽ xử dụng ListView.
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
final items = Product.getProducts();

@override
Widget build(BuildContext context) {
return Scaffold( appBar: AppBar(title:
Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item:
items[index]),
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) =>
ProductPage(item: items[index]),tailieusharefree.blogspot.com

),
);
},
);
},
));
}
}
OKE, bây giờ là phần quan trọng trong bài hôm nay. Bây giờ
chúng ta sẽ sử dụng MaterialPageRoute để chuyển
sang screen chi tiết sản phẩm nhé.
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key:
key);
final Product item;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,tailieusharefree.blogspot.com

crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[

Image.asset("assets/appimages/" +
this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(

this.item.name, style: TextStyle(
fontWeight:
FontWeight.bold
)
),

Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)tailieusharefree.blogspot.com

)
)
]
),
),
),
);
}
}
Và chúng ta đã hoàn thành, dưới đây là toàn bộ code trong ứng
dụng.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price,
this.image);

static List<Product> getProducts() {
List<Product> items = <Product>[];
items.add(
Product(
"Pixel",tailieusharefree.blogspot.com

"Pixel is the most featureful phone
ever",
800,
"pixel.jpg"
)
);
items.add(
Product(
"Laptop",
"Laptop is most productive development
tool",
2000,
"laptop.jpg"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever
for meeting",
1500,
"tablet.jpg"
)
);
items.add(
Product(
"Pendrive",
"iPhone is the stylist phone ever",tailieusharefree.blogspot.com

100,
"pendrive.jpg"
)
);
items.add(
Product(
"Floppy Drive",
"iPhone is the stylist phone ever",
20,
"floppydrive.jpg"
)
);
items.add(
Product(
"iPhone",
"iPhone is the stylist phone ever",
1000,
"iphone.jpg"
)
);
return items;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(tailieusharefree.blogspot.com

title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product Navigation
demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
final items = Product.getProducts();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item:
items[index]),
onTap: () {
Navigator.push(tailieusharefree.blogspot.com

context,
MaterialPageRoute(
builder: (context) =>
ProductPage(item: items[index]),
),
);
},
);
},
)
);
}
}
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key:
key);
final Product item;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(tailieusharefree.blogspot.com

mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/" +
this.item.image,height: 100.0,width:
MediaQuery.of(context).size.width,),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight: FontWeight.bold)),

Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)
)
)
]tailieusharefree.blogspot.com

),
),
),
);
}
}
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() =>
_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}tailieusharefree.blogspot.com

Widget build(BuildContext context) {
double _size = 20;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1 ? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),tailieusharefree.blogspot.com

child: IconButton(
icon: (
_rating >= 2 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,tailieusharefree.blogspot.com

size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key:
key);
final Product item;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" +
this.item.image),tailieusharefree.blogspot.com

Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)
)
)
]
),
)
);
}
}
Sau khi hoàn thành, chúng ta sẽ run ứng dụng lên và click vào
bất kì Product item nào, nó sẽ hiển thị các trang chi tiết về sản
phẩm liên quan và chúng ta có thể trở về màn hình HomePagetailieusharefree.blogspot.com

bằng cách nhấn vào nút quay lại. Dưới đây là hình ảnh để các
bạn có thể dễ dàng quan sát hơntailieusharefree.blogspot.com

tailieusharefree.blogspot.com

Bài 13: Animation

Animation là một lớp trừu tượng và việc xử lý khá phức tạp.
Nhưng thay vào đó, animation giúp nâng cao trải nghiệm của
người dùng, giúp người dùng tương tác với giao diện một cách
thoải mái, hứng thú và không gây nhàm chán. Chúng ta không
thể phủ nhận tầm quan trọng của animation, một ứng dụng tuyệt
vời không chỉ là một ứng dụng chạy nhanh, nhẹ mà yếu tố quan
trọng đó là giao diện phải đẹp, đơn giản và hiệu ứng đa
dạng.Flutter framework ghi nhận điều đó và đã cung cấp cho các
lập trình viên các framework đơn giản và trực quan để dễ dàng
phát triển tất các dạng Animation.
Giới thiệu:
Animation là quá trình thể hiện một loạt các hình ảnh trong một
khoảng thời gian. Một vài điều quan trọng về Animation như sau
:
1. Animation có 2 giá trị đích : đầu và cuối. Ví dụ để hiệu
ứng một widget biến mất thì giá trị đầu của nó có
opacity( độ đục ) tuyệt đối và giá trị cuối có opacity
bằng 0
2. Giá trị trung gian có thể là tuyến tính( đường thẳng)
hoặc không tuyến tính (đường cong) và nó có thể được
cấu hình. Chúng ta hiểu rằng animation làm việc giống
như được cấu hình. Với mỗi cấu hình khác nhau sẽ cho
ra một kiểu animation khác nhau. Ví dụ một widgettailieusharefree.blogspot.com

hiệu ứng mờ dần từ trái sang phải có thể cấu hình thành
hiệu ứng nảy lên giống như quả bóng rồi dần dần biến
mất
3. Thời gian khi chạy animation có tác động đến tốc độ
(nhanh hay chậm) của hiệu ứng
4. Trong flutter , hệ thống animation không có bất kì
animation cụ thể nào cả. Thay vào đó nó cung cấp duy
nhất giá trị yêu cầu cho tất cả các frame để render hình
ảnh
Lớp Animation:
Flutter animation dựa trên các đối tượng animation. Lõi của các
lớp animation nó hoạt động như sau :
Animation:
Tạo ra giá trị và được thêm vào giữa hai số (bắt đầu và kết thúc
animation). Các kiểu animation thường được sử dụng là:
Animation<double> : Thêm các giá trị giữa hai số thập phân
Animation<Color> : Thêm các màu vào giữa hai màu
Animation<Size> : Thêm kích thước vào giữa hai kích thước
AnimationController : Là một đối tượng animation đặc biệt
dùng dể diều khiển các hiệu ứng của chính nó. Nó tạo ra các giá
trị mới bất cứ khi nào ứng dụng sẵn sàng cho một frame
mới.Ngoài ra nó còn hỗ trợ các animation tuyến tính với giá trị từ
0.0 đến 1.0
controller = AnimationController(duration: const
Duration(seconds: 2), vsync: this);tailieusharefree.blogspot.com

Ở đây, controller kiểm soát hiệu ứng trong khoảng thời gian
animation hoạt động. vsynv là một tính năng đặc biệt được dùng
để tối ưu hóa các nguồn sử dụng animation
CurvedAnimation
Nó cơ bản giống như AnimationController nhưng hỗ trợ
animation phi tuyến tính( đường cong). CurvedAnimation có thể
sử dụng cùng với đối tượng animation như sau :
controller = AnimationController(duration: const
Duration(seconds: 2), vsync: this);
animation = CurvedAnimation(parent: controller,
curve: Curves.easeIn)
Tween<T>
Được kế thừa từ Animatable<T> và tạo các giá trị bất kì khác 0 và
1 . Nó được sử dụng cùng với đối tượng animation bởi phương
thức animate
AnimationController controller =
AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this); Animation<int> customTween =
IntTween(
begin: 0, end: 255).animate(controller);
Ngoài ra, Tween cũng có thể sử dụng cùng với CurvedAnimation
như dưới đây :
AnimationController controller =
AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this);tailieusharefree.blogspot.com

final Animation curve = CurvedAnimation(parent:
controller, curve: Curves.easeOut);
Animation<int> customTween = IntTween(begin: 0,
end: 255).animate(curve);
Nhìn trên, controller thực tế là một animation controller, curve
cung cấp các dạng phi tuyến tính và customTween để điều chỉnh
giá trị trị từ 0 đến 255.
Bây giờ ta bắt đầu làm việc với Animation:
Đầu tiên , định nghia controller animation ở hàm initState
trong StatefulWidget
AnimationController(duration: const
Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end:
300).animate(controller);
controller.forward();
Thêm animation bằng listener, addListener để thay đổi trạng thái
của widget
animation = Tween<double>(begin: 0, end:
300).animate(controller) ..addListener(() {
setState(() {
// The state that has changed here is the
animation object’s value.
});
});
-Xây dựng widget gồm hai hàm là hàm AnimatedWidget
và AnimatedBuilder . Cả hai widget đều chấp nhận đối tượng
animation và nhận giá trị hiện tại cần thiết cho animation tailieusharefree.blogspot.com

-Nhận các giá trị animation trong khi chạy các widget và sau đó
sử dụng nó cho độ dài(height) và độ rộng(width) hoặc bất kì các
thuộc tính liên quan thay vì các giá trị ban đầu
child: Container(
height: animation.value,
width: animation.value,
child: <Widget>,
)
Làm việc với ứng dụng :
Bây giờ chúng ta sẽ tiến hành viết một ứng dụng đơn giản để hiểu
về khái niệm animationtrong flutter framework nhé ^^
Các bạn quay lại bài trước để lấy assets cũng như ảnh ở bài 7 nhé,
mình sẽ để link ảnh dưới đây(https://vncoder.vn/bai-hoc/layout-
trong-flutter-225)
Mình tạo project với tên là flutter demo, sau đó xóa code mặc
định, thêm import và hàm main cơ bản
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
Tạo MyApp widget kế thừa từ StateFullWidget( widget sẽ thay
đổi giao diện khi được rebuild)
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
Tạo _MyAppState widget và implement initState, sau đó chúng ta
thêm hàm dispose(đây là hàm đóng controller)
tailieusharefree.blogspot.com

class _MyAppState extends State<MyApp> with
SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this
);
animation = Tween<double>(begin: 0.0, end:
1.0).animate(controller);
controller.forward();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
controller.forward();
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch:
Colors.blue,),
home: MyHomePage(title: 'Product layout
demo home page', animation: animation,)
);
}
@override
void dispose() {tailieusharefree.blogspot.com

controller.dispose();
super.dispose();
}
}class _MyAppState extends State<MyApp> with
SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this
);
animation = Tween<double>(begin: 0.0, end:
1.0).animate(controller);
controller.forward();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
controller.forward();
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch:
Colors.blue,),
home: MyHomePage(title: 'Product layout
demo home page', animation: animation,)
); tailieusharefree.blogspot.com

}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
Oke, mình cùng giải thích một tẹo về code đoạn code trên :
1. Hàm initState được dùng để tạo đối tượng animation
controller (controller), đối tượng animation(animation)
và để bắt đầu với animation, chúng ta sử dụng phương
thức controller.forward.
2. Ở hàm dispose, như mình nói ở trên, sau khi tạo thì
chúng ta phải hủy nó nên đó là chức năng của hàm
dispose để hủy bỏ controller
3. Ở hàm Build, animatio được gửi tới MyHomePage
widget thông quan constructor. Bây giờ, MyHomePage
có thể sử dụng đối tượng animation tạo hiệu ứng cho
nội dung
4. Giờ chúng ta sẽ tạo ProductBox widget

class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name,
this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;tailieusharefree.blogspot.com

final int price;
final String image;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" + image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name,
style:

TextStyle(fontWeight: FontWeight.bold)),

Text(this.description),tailieusharefree.blogspot.com

Text("Price: " +
this.price.toString()),
],
)
)
)
]
)
)
);
}
}
Tiếp theo chúng ta sẽ tạo widget mới có tên
là MyAnimatedWidget với hiệu ứng đơn giản fade animation sử
dụng opacity(độ đục)
class MyAnimatedWidget extends StatelessWidget {
MyAnimatedWidget({this.child, this.animation});

final Widget child;
final Animation<double> animation;

Widget build(BuildContext context) => Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) => Container(
child: Opacity(opacity: animation.value,
child: child),
), tailieusharefree.blogspot.com

child: child),
);
}
Nhìn trên, chúng ta đã sử dụng AniatedBuilder để tạo hiệu
ứng. AniatedBuilder là mọt widget được sử dụng để build nội
dung và hiệu ứng cùng một thời điểm. Nó cho phép đối tượng
animation có thể lấy giá trị của animation hiện tại. Chúng ta sử
dụng giá trị animation thông qua animation.value để set
opacity(độ đục) của widget con. Và animation sẽ tác động lên
child widget đang sử dụng opacity
Cuối cùng chúng ta sẽ tạo MyHomePage widget và sử dụng đối
tượng animation để tạo hiệu ứng cho nội dung của ứng dụng

class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title,
this.animation}) : super(key: key);

final String title;
final Animation<double>
animation;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Listing")),body: ListView(
shrinkWrap: true,tailieusharefree.blogspot.com

padding: const
EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(
child: ProductBox(
name: "iPhone",
description: "iPhone is the
stylist phone ever",
price: 1000,
image: "iphone.jpg"
), opacity: animation
),
MyAnimatedWidget(child: ProductBox(
name: "Pixel",
description: "Pixel is the most
featureful phone ever",
price: 800,
image: "pixel.jpg"
), animation: animation),
ProductBox(
name: "Laptop",
description: "Laptop is most
productive development tool",
price: 2000,
image: "laptop.jpg"
),
ProductBox(
name: "Tablet",tailieusharefree.blogspot.com

description: "Tablet is the most
useful device ever for meeting",
price: 1500,
image: "tablet.jpg"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful
storage medium",
price: 100,
image: "pendrive.jpg"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is
useful rescue storage medium",
price: 20,
image: "floppydrive.jpg"
),
],
)
);
}
}
Ở đây chúng ta sử dụng FadeAnimation và MyAnimationWidget
để tạo hiệu ứng cho 2 items đầu trong list các
product. FadeAnimation được xây dựng trong lớp animation màtailieusharefree.blogspot.com

chúng ta đã từng tạo hiệu ứng cho widget con đang sử dụng
opacity
Vậy là xong, dưới đây là toàn bộ code chúng ta đã hao

import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with
SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;

@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this);
animation = Tween<double>(begin: 0.0, end:
1.0).animate(controller);
controller.forward();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {tailieusharefree.blogspot.com

controller.forward();
return MaterialApp(
title: 'Flutter Demo', theme:
ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout
demo home page', animation: animation,)
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title,
this.animation}): super(key: key);
final String title;
final Animation<double> animation;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Listing")),
body: ListView(
shrinkWrap: true,tailieusharefree.blogspot.com

padding: const
EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(
child: ProductBox(
name: "iPhone",
description: "iPhone is the
stylist phone ever",
price: 1000,
image: "iphone.jpg"
),
opacity: animation
),
MyAnimatedWidget(
child: ProductBox(
name: "Pixel",
description: "Pixel is the
most featureful phone ever",
price: 800,
image: "pixel.jpg"
),
animation: animation
),
ProductBox(
name: "Laptop",
description: "Laptop is most
productive development tool",
price: 2000,tailieusharefree.blogspot.com

image: "laptop.jpg"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most
useful device ever for meeting",
price: 1500,
image: "tablet.jpg"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful
storage medium",
price: 100,
image: "pendrive.jpg"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is
useful rescue storage medium",
price: 20,
image: "floppydrive.jpg"
),
],
)
);
}
}tailieusharefree.blogspot.com

class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name,
this.description, this.price, this.image}) :
super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" + image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(tailieusharefree.blogspot.com

this.name, style:
TextStyle(
fontWeight:
FontWeight.bold
)
),

Text(this.description), Text(
"Price: " +
this.price.toString()
),
],
)
)
)
]
)
)
);
}
}
class MyAnimatedWidget extends StatelessWidget {
MyAnimatedWidget({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) => Center(
child: AnimatedBuilder(
animation: animation, tailieusharefree.blogspot.com

builder: (context, child) => Container(
child: Opacity(opacity:
animation.value, child: child),
),
child: child
),
);
}
Sau khi hoàn thành và chạy ứng dụng thì ta sẽ có kết quả như
dưới đâytailieusharefree.blogspot.com

tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

Bài 14: Code với native Android

Flutter cung cấp framework chung để truy cập vào các nền
tảng có tính năng riêng biệt. Việc này giúp cho các lập trình viên
có thể mở rộng các chức năng sử dụng nền tảng lập trình cụ thể
như camera, pin, trình duyệt web,...Có thể dễ dàng truy cập
thông qua framework
Ý tưởng chung để truy cập vào mã cụ thể của nền tảng thông qua
giao thức đơn giản là messaging.Flutter code, Client , mã nền
tảng và Host liên kết với một thông báo chung gọi là Message
Channel. Client sẽ gửi thông báo đến Host thông qua Message
Channel. Host sẽ lắng nghe từ Message Channel, nhận thông
báo và xử lý các hàm cần thiết và cuối cùng trả kết quả về
cho Clients thông qua Message Channel.
Dưới đây là kiến trúc platform specific code được hiển thị thông
qua sơ đồi khối :
Giao thức thông báo sử dụng mã thông báo tiêu chuẩn(
lớp StandardMessageCodec), được hỗ trợ tuần tự nhị phân
của JSON - như các giá trị kiểu số , chuỗi, boolean,..serialization
và de-serialization hoạt động rõ ràng giữa Clients và Host.tailieusharefree.blogspot.com

Hôm nay chúng ta sẽ thử viết ứng dụng đơn giản để mở trình
duyệt web sử dụng Android SDK
Đầu tiên chúng ta sẽ tạo ứng dụng với
tên "flutter_browser_app" nhé
Sau đó thay thế đoạn code trong hàm main.dart thành :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo
Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(tailieusharefree.blogspot.com

appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: null,
),
),
);
}
}
Ở đoạn code trên, ta đã tạo một nút có chức năng để mở trình
duyệt và tạm thời ta set nó ở trạng thái NULL
Bây giờ ta sẽ import đoạn code dưới đây :
import 'dart:async';
import 'package:flutter/services.dart';
Tiếp theo ta sẽ viết phương thức _openBrowser để gọi nền tảng
cụ thể thông qua message channel.
Future<void> _openBrowser() async {
try {
final int result = await
platform.invokeMethod(
'openBrowser', <String, String>{
'url': "https://flutter.dev"
}
);
} tailieusharefree.blogspot.com

on PlatformException catch (e) {
// Unable to open the browser
print(e);
}
}
Ở đây chúng ta sử dụng platform.invokeMethod để
gọi openBrowser (giải thích ở bước tiếp theo ), openBrowser có
đối số, url để mở url cụ thể
Giờ chúng ta sẽ thay đổi đối
số null trong RaiseButton thành _openBrowser
onPressed: _openBrowser,
Tiếp đến bạn hãy mở MainActivity,java (bên trong thư mục
android) và import một số thư viện sau đây:
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import
io.flutter.plugin.common.MethodChannel.MethodCallH
andler;
import
io.flutter.plugin.common.MethodChannel.Result;
import
io.flutter.plugins.GeneratedPluginRegistrant;tailieusharefree.blogspot.com

Bây giờ chúng ta sẽ viết hàm openBrowser để mở trình duyệt
nhé :
private void openBrowser(MethodCall call, Result
result, String url) {
Activity activity = this;
if (activity == null) {
result.error("ACTIVITY_NOT_AVAILABLE",
"Browser cannot be opened without foreground
activity", null);
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));

activity.startActivity(intent);
result.success((Object) true);
}
Trong hàm MainActivity ta đặt tên Channel ;
private static final String CHANNEL =
"flutterapp.tutorialspoint.com/browser";
Ở hàm Oncreate ta sẽ viết mã cụ thể cho Android để xử lý
message
new MethodChannel(getFlutterView(),
CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call,
Result result) {tailieusharefree.blogspot.com

String url = call.argument("url");
if (call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}
}
})
Nhìn trên, chúng ta đã tạo message channe sử dụng
lớp MethodChannel và lớp MethodCallHandler để xử lý
thông báo. onMethodCall có trách nhiệm gọi đúng mã nền tảng
riêng biệt bẳng cách kiếm tra thông báo.
Hàm onMethodCall đọc url từ thông báo và gọi
đến openBrowser khi mà hàm gọi openBrowser. Ngược lại hàm
sẽ trả về method notImplemented
OKI giờ chúng ta xem toàn bộ code nhé :
MainActivity.java
package
com.tutorialspoint.flutterapp.flutter_browser_app;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;tailieusharefree.blogspot.com

import
io.flutter.plugin.common.MethodChannel.Result;
import
io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity
{
private static final String CHANNEL =
"flutterapp.tutorialspoint.com/browser";
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);

GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(),
CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall
call, Result result) {
String url = call.argument("url");
if
(call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}tailieusharefree.blogspot.com

}
}
);
}
private void openBrowser(MethodCall call,
Result result, String url) {
Activity activity = this; if (activity ==
null) {
result.error(
"ACTIVITY_NOT_AVAILABLE", "Browser
cannot be opened without foreground activity",
null
);
return;
}
Intent intent = new
Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
activity.startActivity(intent);
result.success((Object) true);
}
}
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());tailieusharefree.blogspot.com

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: 'Flutter Demo Home Page'
),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
static const platform = const
MethodChannel('flutterapp.tutorialspoint.com/brows
er');
Future<void> _openBrowser() async {
try {
final int result = await
platform.invokeMethod('openBrowser', <String,
String>{
'url': "https://flutter.dev"
});tailieusharefree.blogspot.com

}
on PlatformException catch (e) {
// Unable to open the browser print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: _openBrowser,
),
),
);
}
}
Giờ chúng ta chạy thử ứng dụng. Sau khi run ứng dụng , ta sẽ
thấy nút Open browser như hình dưới, rồi ấn vào nó tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

Bài 15: Code với native IOS

Việc truy cập vào các nền tảng riêng của hệ điều hành IOS cũng
giống như Android nhưng ta sẽ sử dụng object C hay swift (ngôn
ngữ dành riêng cho lập trình IOS) và IOS sdk. Tuy nhiên về
khái niệm thì như nhau
Nào, chúng ta sẽ bắt đầu viết một ứng dụng tương tự bài học
trước nhưng sử dụng cho nền tảng IOS nhé
1. Tạo ứng dụng mới trên Android studio (enviroment)
trên MacOS với tên "flutter_browser_ios_app"
2. Từ bước 2 đến bước 6 các bạn làm giống như bài 14(
bài trước )
3. Sau đó các bạn khởi động Xcode , nhấn File->Open
4. Chọn Xcode project phía dưới ios director của flutter
project
5. Mở AppDelegate.m dưới Runner -> Runner path. Và nó
sẽ chứa dòng code sau
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary
*)launchOptions {tailieusharefree.blogspot.com

// [GeneratedPluginRegistrant
registerWithRegistry:self];
// Override point for customization after
application launch.
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
@end
Chúng ta sẽ thêm hàm openBrowser để mở trình duyệt web với
url. Nó chấp nhận đối số duy nhất là url
- (void)openBrowser:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
UIApplication *application = [UIApplication
sharedApplication];
[application openURL:url];
}
Trong hàm didFinishLaunchingWithOptions, tìm cotroller và đặt
nó vào bên trong biến controller
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewContro
ller;
Trong hàm didFinishLaunchingWithOptions , đặt browser chanel
là flutterapp.tutorialspoint.com/browse
FlutterMethodChannel* browserChannel = [
FlutterMethodChannel methodChannelWithName:
@"flutterapp.tutorialspoint.com/browser"
binaryMessenger:controller];
Tạo biến weakSelf và đặt class hiện tạitailieusharefree.blogspot.com

__weak typeof(self) weakSelf = self;
Bây giờ ta sẽ implement setMethodCallHandler. Gọi
hàm openBrowser bởi call.method. Lấy giá trị url bằng
call.arguments và bỏ qua nó khi gọi openBrowser
[browserChannel
setMethodCallHandler:^(FlutterMethodCall* call,
FlutterResult result) {
if ([@"openBrowser"
isEqualToString:call.method]) {
NSString *url = call.arguments[@"url"];
[weakSelf openBrowser:url];
} else { result(FlutterMethodNotImplemented); }
}];
Dưới đây là toàn bộ code mẫu, mời bạn đọc cùng tham khảo
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary
*)launchOptions {

// custom code starts
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewContro
ller;
FlutterMethodChannel* browserChannel = [
FlutterMethodChannel methodChannelWithName:tailieusharefree.blogspot.com

@"flutterapp.tutorialspoint.com /browser"
binaryMessenger:controller];

__weak typeof(self) weakSelf = self;
[browserChannel setMethodCallHandler:^(
FlutterMethodCall* call, FlutterResult
result) {

if ([@"openBrowser"
isEqualToString:call.method]) {
NSString *url = call.arguments[@"url"];
[weakSelf openBrowser:url];
} else {
result(FlutterMethodNotImplemented); }
}];
// custom code ends
[GeneratedPluginRegistrant
registerWithRegistry:self];

// Override point for customization after
application launch.
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
- (void)openBrowser:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
UIApplication *application = [UIApplication
sharedApplication];tailieusharefree.blogspot.com

[application openURL:url];
}
@end
1. Mở project setting
2. Tìm đến Capabilities bật Background Modes.
3. Thêm *Background fetch và Remote Notification**
4. Nào, bây giờ ta thử run ứng dụng và nó sẽ làm việc
giống như phiên bản Android nhưng là trình
duyệt Safari thay vì chrome nhé
tailieusharefree.blogspot.com

Bài 16: Giới thiệu về package

Cách mà Dart tổ chức và chia sẻ các chức năng thông package
. Dart Package là một thư viện hay mô hình đơn giản có thể chia
sẻ. Nhìn chung, Dart package cũng giống như Dart Application
ngoại trừ Dart Package không truy cập vào các điểm chính cùa
ứng dung
Cấu trúc chung của Package ( ví dụ về package) dưới đây :
- lib/src/* : tệp Dart ở dạng priavte
- lib/my_demo_package.dart : phần code chính của Dart, có
thể thêm một vài ứng dụng
import
'package:my_demo_package/my_demo_package.dart'
- Một vài tệp ở dạng private có thể được xuất sang tệp chính
(my_demo_package.dart) :
export src/my_private_code.dart
- lib/* : Ta có thể truy cập vào bất kì tệp nào bên trong thư mục
:
import
'package:my_demo_package/custom_folder/custom_file
.dart'
- pubspec.yaml : Được hiểu là trình quản lý thư mục của
Package
Để tích hợp được các gói vào dự án thì ta cần phải có file
pubspec.yaml tailieusharefree.blogspot.com

Các kiểu Package :
Kể từ khi Dart package là một collection có chức năng tương tự ,
nó có thể được phân loại dựa trên chức năng:
Dart Package
Chúng ta có thể sử dụng Dart trên cả 2 môi trường là web và
android. Ví dụ , english_words là một package chứa khoảng 500
từ và có chức năng tiệng ích cơ bản như danh từ ( list các danh từ
trong English), âm tiết (liệt kê ra các từ có âm tiết đặc biệt )
Flutter package
Phụ thuộc vào Flutter framework và có thể chỉ sử dụng trong
môi trường mobile .
Flutter plugin
Phụ thuộc vào Flutter framework cũng như nền tảng cơ bản
(Android SDK hay iOS SDK). Ví dụ Camera là một plugin (có
thể hiểu là một phần mềm hỗ trợ) để tương tác với thiết bị
camera. Nó sử dụng SDK để có quyền truy cập vào camera
Sử dụng Dart Package :
Dart package được lưu trữ và publish trên các máy
chủ, https://pub.dev . Ngoài ra, Flutter cung cấp các tool, pub cơ
bản để quản lý các Dart package trong ứng dụng. Các bước cần
để sử dụng Package như sau :
-Nhập tên package và phiên bản phù hợp trong file pubspec.yaml
như dưới đây :
dependencies: english_words: ^3.1.5
-Bản mới nhất sẽ được cập nhật trên server
- Cài đặt package bằng lệnh :tailieusharefree.blogspot.com

flutter packages get
- Khi chúng ta đang dùng Android studio, thì Android studio sẽ
phát hiện bất kì thay đổi trong file pubspec.yaml và hiện thông
báo để lập trình viên có thể biết
- Dart package có thể được cài đặt hoặc nâng cấp trong Android
studio thông qua menu optionsoptions .
- Thêm các file cần thiết sử dụng lệnh dưới đây và bắt đầu làm
việc :
import 'package:english_words/english_words.dart';
- Sử dụng bất kì phương thức có sẵn
nouns.take(50).forEach(print);
- Ở trên ta đã dùng hàm nouns để lấy ra 50 từ đầu tiên
Làm việc với Flutter plugin package
Làm việc với Flutter plugin cũng giống như làm việc với Dart
package hay Dart application. Chỉ khác ở chỗ Plugin sẽ sử dụng
System API(Android hay iOS) để có được những chắc năng cụ
thể cần thiết
Như chúng ta đã được tìm hiểu cách truy cập vào các nền tảng
riêng của hệ điều hành ở bài trước, hôm nay chúng ta sẽ tự xây
dựng một plugin đơn giản là my_browser . Chức năng
của my_browser là cho phép ứng dụng mở trình duyện riêng của
nền tảng ( IOS hay Android)
- Đầu tiên mở Android studio
- Nhấn file -> New flutter project và chọn futter plugin tailieusharefree.blogspot.com

- Nhập tên project "my_browser" và nhấn Next
- Nhập tên plugin và các thông tin khác như sau :
- Nhập tên miền công ty tên bất kì bạn muốn có dạng
tenproject.tendomaincompany.com (không quan trọng)tailieusharefree.blogspot.com

- Mở file my_browser.dart và viết các phương thức, openBrowser
dùng để gọi phương thức trong nền tảng riêng ( android hay ios )
Future<void> openBrowser(String urlString) async {
try {
final int result = await
_channel.invokeMethod(
'openBrowser', <String, String>{ 'url':
urlString }
);
}
on PlatformException catch (e) {
// Unable to open the browser print(e);
}
}
- Mở file MyBrowserPlugin.java và import một vài lớp như sau :
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;tailieusharefree.blogspot.com

- Ở đây , chúng ta phải thêm thư viện cần thiết để mở browser từ
Android
- Thêm các giá trị mRegistrar thuộc kiểu Registrar trong
lớp MyBrowserPlugin ở dạng private
private final Registrar mRegistrar;
- Ở trên Registrar được dùng để lấy thông tin context của code
được gọi
- Thêm constructor
private MyBrowserPlugin(Registrar registrar) {
this.mRegistrar = registrar;
}
- Thay đổi registerWith bao gồm constructor mới trong lớp
MyBrowserPlugin
public static void registerWith(Registrar
registrar) {
final MethodChannel channel = new
MethodChannel(registrar.messenger(),
"my_browser");
MyBrowserPlugin instance = new
MyBrowserPlugin(registrar);
channel.setMethodCallHandler(instance);
}
- Thay đổi onMethodCall gồm hàm openBrowser trong
lớp MyBrowserPlugin
@Override
public void onMethodCall(MethodCall call, Result
result) {tailieusharefree.blogspot.com

String url = call.argument("url");
if (call.method.equals("getPlatformVersion")) {
result.success("Android " +
android.os.Build.VERSION.RELEASE);
}
else if (call.method.equals("openBrowser")) {
openBrowser(call, result, url);
} else {
result.notImplemented();
}
}
- Sau đó viết hàm openBrowser để truy cập vào browser trong
lớp MyBrowserPlugin
private void openBrowser(MethodCall call, Result
result, String url) {
Activity activity = mRegistrar.activity();
if (activity == null) {
result.error("ACTIVITY_NOT_AVAILABLE",
"Browser cannot be opened without foreground
activity", null);
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
activity.startActivity(intent);
result.success((Object) true);
}
Toàn bộ source code của my_browser plugin như sau :tailieusharefree.blogspot.com

my_browser.dart
import 'dart:async';
import 'package:flutter/services.dart';

class MyBrowser {
static const MethodChannel _channel = const
MethodChannel('my_browser');
static Future<String> get platformVersion async
{
final String version = await
_channel.invokeMethod('getPlatformVersion');
return version;
}
Future<void> openBrowser(String urlString)
async {
try {
final int result = await
_channel.invokeMethod(
'openBrowser', <String, String>{'url':
urlString});
}
on PlatformException catch (e) {
// Unable to open the browser print(e);
}
}
}
MyBrowserPlugin.javatailieusharefree.blogspot.com

package
com.tutorialspoint.flutterplugins.my_browser;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import
io.flutter.plugin.common.MethodChannel.MethodCallH
andler;
import
io.flutter.plugin.common.MethodChannel.Result;
import
io.flutter.plugin.common.PluginRegistry.Registrar;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

/** MyBrowserPlugin */
public class MyBrowserPlugin implements
MethodCallHandler {
private final Registrar mRegistrar;
private MyBrowserPlugin(Registrar registrar) {
this.mRegistrar = registrar;
}
/** Plugin registration. */
public static void registerWith(Registrar
registrar) {tailieusharefree.blogspot.com

final MethodChannel channel = new
MethodChannel(
registrar.messenger(), "my_browser");
MyBrowserPlugin instance = new
MyBrowserPlugin(registrar);
channel.setMethodCallHandler(instance);
}
@Override
public void onMethodCall(MethodCall call,
Result result) {
String url = call.argument("url");
if
(call.method.equals("getPlatformVersion")) {
result.success("Android " +
android.os.Build.VERSION.RELEASE);
}
else if (call.method.equals("openBrowser"))
{
openBrowser(call, result, url);
} else {
result.notImplemented();
}
}
private void openBrowser(MethodCall call,
Result result, String url) {
Activity activity = mRegistrar.activity();
if (activity == null) {
result.error("ACTIVITY_NOT_AVAILABLE",tailieusharefree.blogspot.com

"Browser cannot be opened without
foreground activity", null);
return;
}
Intent intent = new
Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
activity.startActivity(intent);
result.success((Object) true);
}
}
- Tạo project mới, lấy tên tuỳ bạn , ở đây mình sẽ
dùng my_browser_plugin_test để đặt tên cho project
- Trong file pubspec.yaml ta thêm plugin my_browser trong
dependency.
dependencies:
flutter:
sdk: flutter
my_browser:
path: ../my_browser
- Android studio sẽ thông báo rằng file pubspec.yam cần được
cập nhật và hiển thị bạn chỉ cần click vào get dependency
- Trong file main.dart và my_browser plugin ta cần thêm thư viện
vào :
import 'package:my_browser/my_browser.dart';
- Gọi hàm openBrowser từ my_browser plugin như sau :tailieusharefree.blogspot.com

onPressed: () =>
MyBrowser().openBrowser("https://flutter.dev"),
Okey, dưới đây là toàn bộ code trong hàm main.dartdart
import 'package:flutter/material.dart';
import 'package:my_browser/my_browser.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: 'Flutter Demo Home Page'
),
);,
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
@override
Widget build(BuildContext context) {tailieusharefree.blogspot.com

return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: RaisedButton(
child: Text('Open Browser'),
onPressed: () =>
MyBrowser().openBrowser("https://flutter.dev"),
),
),
);
}
}
- Sau khi hoàn chỉnh ta sẽ chạy ứng dụng lên và click và nút
open browser (hiển thị ở giữa màn hình) như hình dưới đây.tailieusharefree.blogspot.com

- Sau khi nhất nút open browser sẻ mở ra trình duyệt như ảnh
dưới đây :tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

Bài 17: REST API

Flutter cung cấp package http để sử dụng nguồn HTTP . http là
một thư viện Future-based sử dụng tính năng await và async. Nó
cung cấp phương thức cấp cao và đơn giản để phát triển REST
trên ứng dụng di động.
Nội dung cơ bản :
Gói http cung cấp các lớp cấp cao và http request từ web
- Lớp http cung cấp chức năng để làm việc với tất cả các kiểu dữ
liệu HTTP được request
- Phương thức http có sử dụng url , và bổ sung thông tin thông
qua Dart Map ( post dữ liệu, bổ sung tiêu đề, ...). Nó yêu cầu lên
máy chủ và thu thập phản hồi với async/await. Ví dụ đoạn code
dưới đây đọc dữ liệu từ url và in nó trên console
print(await http.read('https://flutter.dev/'));
Một vài phương thức chính :
- read : gởi yêu cầu lên sever thông qua phương thức GET và trả
về Future<String>
- get : gởi yêu cầu lên sever thông qua phương thức GET và trả
về Future<Response>. Response là lớp giữ lại các thông tin phản
hồi
- post : gởi yêu cầu lên sever thông qua phương thức POST bằng
việc đưa giá trị lên sever và phản hồi Future<Response>
- put : gởi yêu cầu lên sever thông qua phương thức PUT và trả
về phản hồi như Future<Response>tailieusharefree.blogspot.com

- head : gởi yêu cầu lên sever thông qua phương thức HEAD và
trả về phản hồi như Future<Response>
- delete : gởi yêu cầu lên sever thông qua phương thức DELETE
và trả về phản hồi như Future<Response>
Http cũng cung cấp nhiều lớp standard HTTP client. client có
nhiệm vụ hỗ trợ kết nối. Nó sẽ hữu ích khi có rất nhiều request
lên sever
var client = new http.Client();
try {
print(await
client.get('https://flutter.dev/'));
}
finally {
client.close();
}
Truy cập vào Product service API
Ta sẽ tạo ứng dụng đơn giản để lấy dữ liệu Product từ web server
và sau đó hiển thị danh sách Product trong ListView
Tạo ứng dụng Flutter trong android với tên "product_rest_app"
Copy assets dưới đây vào pubspec.yaml ,(các bạn có thể lấy ảnh
ở bài 7 )
flutter:
assets:
- assets/appimages/floppy.jpg
- assets/appimages/iphone.jpg
- assets/appimages/laptop.jpg
- assets/appimages/pendrive.jpgtailieusharefree.blogspot.com

- assets/appimages/pixel.jpg
- assets/appimages/tablet.jpg
Thêm http package vào file pubspec.yaml như sau :
dependencies:
http: ^0.12.0+2
Thêm một vào package trong hàm main.dart
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Tạo file JSON, product.json
[
{
"name": "iPhone",
"description": "iPhone is the stylist phone
ever",
"price": 1000,
"image": "iphone.png"
},
{
"name": "Pixel",
"description": "Pixel is the most feature
phone ever",
"price": 800,
"image": "pixel.png"
},
{
"name": "Laptop",tailieusharefree.blogspot.com

"description": "Laptop is most productive
development tool",
"price": 2000,
"image": "laptop.png"
},
{
"name": "Tablet",
"description": "Tablet is the most useful
device ever for meeting",
"price": 1500,
"image": "tablet.png"
},
{
"name": "Pendrive",
"description": "Pendrive is useful storage
medium",
"price": 100,
"image": "pendrive.png"
},
{
"name": "Floppy Drive",
"description": "Floppy drive is useful
rescue storage medium",
"price": 20,
"image": "floppy.png"
}
]tailieusharefree.blogspot.com

Tạo thư mục mới tên là JSONWebServer , đặt products.json vào
trong
Chạy bất kì web server với JSONWebServer và lấy đường dẫn.
Ví dụ http://192.168.184.1:8000/products.json. Một vài web
server như apache, nginx, ...
Cách đơn giản nhất là cài đặt nodejs dựa trên ứng dụng http-
server. Các bước cài đặt như sau :
- Cài đặt ứng dụng NodeJs https://nodejs.org/en
- Đi đến thư mục JSONWebServer
cd /path/to/JSONWebServer
- Cài đặt http-server package bằng cách sử dụng npm
npm install -g http-server
- Sau đó thử chạy server
http-server . -p 8000

Starting up http-server, serving .
Available on:
http://192.168.99.1:8000
http://127.0.0.1:8000
Hit CTRL-C to stop the server
Tạo file mới Product.dart trong lớp Product
Viết factory constructor trong lớp Product , Product.fromMap
dùng để chuyển đổi dữ liệu map trong đối tượng Product . Thông
thường, tệp JSON sẽ được chuyển đổi bên trong đối tượng Dart
Map và sau đó chuyển đổi sang đối tượng liên qua (Product)
factory Product.fromJson(Map<String, dynamic>
data) {tailieusharefree.blogspot.com

return Product(
data['name'],
data['description'],
data['price'],
data['image'],
);
}
Code trong hàm product.dart như sau :
class Product {
final String name;
final String description;
final int price;
final String image;

Product(this.name, this.description,
this.price, this.image);
factory Product.fromMap(Map<String, dynamic>
json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
);
}
}
Bây giờ ta sẽ viết 2 phương thức - parseProducts và fetchProducts
- trong lớp chính để lấy và tải thông tin sản phẩm từ webtailieusharefree.blogspot.com

server(máy chủ) trong List<Product>
List<Product> parseProducts(String responseBody) {
final parsed =
json.decode(responseBody).cast<Map<String,
dynamic>>();
return parsed.map<Product>((json)
=>Product.fromJson(json)).toList();
}
Future<List<Product>> fetchProducts() async {
final response = await
http.get('http://192.168.1.2:8000/products.json');
if (response.statusCode == 200) {
return parseProducts(response.body);
} else {
throw Exception('Unable to fetch products
from the REST API');
}
}
Ta nên lưu ý một vài điểm sau :
-Future được sử dụng để trì hoãn việc tải thông tin sản phẩm cho
đến khi cần thiết
- http.get được dùng để lấy dữ liệu từ internet
- json.decode được sử dụng để dịch dữ liệu JSON trong Dart
Map. Mỗi một dữ liệu JSON được dịch , nó sẽ chuyển
vào List<Product> bằng fromMap của lớp Product
Trong lớp MyApp, thêm giá trị product mới, các product thuộc
kiểu Future<Product> và đưa vào hàm constructortailieusharefree.blogspot.com

class MyApp extends StatelessWidget {
final Future<List<Product>> products;
MyApp({Key key, this.products}) : super(key:
key);
...
Trong lớp MyHomePage, thêm một vài Product thuộc kiểu
Future<Product> và đưa vào constructor .
class MyHomePage extends StatelessWidget {
final String title;
final Future<ListList<Product>> products;
MyHomePage({Key key, this.title,
this.products}) : super(key: key);
...
Trong widget home (MyHomePage) thay đổi như sau
home: MyHomePage(title: 'Product Navigation demo
home page', products: products),
Thay đổi hàm chính trên StateFullWidge
void main() => runApp(MyApp(fetchProduct()));
Tạo widget mới là ProductBoxList để xây dựng list Product trong
home page
class ProductBoxList extends StatelessWidget {
final List<Product> items;
ProductBoxList({Key key, this.items});

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,tailieusharefree.blogspot.com

itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item:
items[index]),
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) =gt;
ProductPage(item: items[index]),
),
);
},
);
},
);
}
}
Lưu ý rằng, chúng ta sử dụng cùng nội dung trong ứng
dụng Navigation để đưa ra list Product
Cuối cùng ta sửa đổi MyHomePage widget's để lấy thông tin
Product sử dụng tính năng Future thay vì phương thức gọi thông
thường
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: Center(
child: FutureBuilder<List<Product>>(tailieusharefree.blogspot.com

future: products, builder: (context,
snapshot) {
if (snapshot.hasError)
print(snapshot.error);
return snapshot.hasData ?
ProductBoxList(items: snapshot.data)

// return the ListView widget :
Center(child:
CircularProgressIndicator());
},
),
)
);
}
- Ở đây ta đã sử dụng widget FutureBuilder để render widget
. FutureBuilder sẽ cố lấy dữ liệu từ thuộc tính future
(thuộc Future<List<Product>>) . Nếu thuộc tính future trả dữ
liệu về , nó sẽ render widget sử dụng ProductBoxList , mặt khác
sẽ ném lỗi(throw err)
Toàn bộ code trong hàm main.dart như dưới đây :
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'Product.dart';
tailieusharefree.blogspot.com

void main() => runApp(MyApp(products:
fetchProducts()));

List<Product> parseProducts(String responseBody) {
final parsed =
json.decode(responseBody).cast<Map<String,
dynamic>>();
return parsed.map<Product>((json) =>
Product.fromMap(json)).toList();
}
Future<List<Product>> fetchProducts() async {
final response = await
http.get('http://192.168.1.2:8000/products.json');
if (response.statusCode == 200) {
return parseProducts(response.body);
} else {
throw Exception('Unable to fetch products
from the REST API');
}
}
class MyApp extends StatelessWidget {
final Future<List<Product>> products;
MyApp({Key key, this.products}) : super(key:
key);

// This widget is the root of your application.
@override
Widget build(BuildContext context) {tailieusharefree.blogspot.com

return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product
Navigation demo home page', products: products),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
final Future<List<Product>> products;
MyHomePage({Key key, this.title,
this.products}) : super(key: key);

// final items = Product.getProducts();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: Center(
child: FutureBuilder<List<Product>>(
future: products, builder:
(context, snapshot) {
if (snapshot.hasError)
print(snapshot.error);tailieusharefree.blogspot.com

return snapshot.hasData ?
ProductBoxList(items: snapshot.data)

// return the ListView widget :
Center(child:
CircularProgressIndicator());
},
),
)
);
}
}
class ProductBoxList extends StatelessWidget {
final List<Product> items;
ProductBoxList({Key key, this.items});

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item:
items[index]),
onTap: () {
Navigator.push(
context, MaterialPageRoute( tailieusharefree.blogspot.com

builder: (context) =>
ProductPage(item: items[index]),
),
);
},
);
},
);
}
}
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key:
key);
final Product item;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:
Text(this.item.name),),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[tailieusharefree.blogspot.com

Image.asset("assets/appimages/" +
this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[

Text(this.item.name, style:

TextStyle(fontWeight: FontWeight.bold)),

Text(this.item.description),
Text("Price: " +
this.item.price.toString()),
RatingBox(),
],
)
)
)
]
),
),
), tailieusharefree.blogspot.com

);
}
}
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState()
=>_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
print(_rating); tailieusharefree.blogspot.com

return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,

children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1
? Icon(Icons.star, ize:
_size,)
: Icon(Icons.star_border,
size: _size,)
),
color: Colors.red[500],
onPressed: _setRatingAsOne, iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2
? Icon(Icons.star, size:
_size,) tailieusharefree.blogspot.com

: Icon(Icons.star_border,
size: _size, )
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(Icons.star, size:
_size,)
: Icon(Icons.star_border,
size: _size,)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {tailieusharefree.blogspot.com

ProductBox({Key key, this.item}) : super(key:
key);
final Product item;

Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2), height: 140,
child: Card(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/"
+ this.item.image),
Expanded(
child: Container(
padding:
EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name,
style:TextStyle(fontWeight: FontWeight.bold)),

Text(this.item.description),
Text("Price: " +
this.item.price.toString()),tailieusharefree.blogspot.com

RatingBox(),
],
)
)
)
]
),
)
);
}
}
tailieusharefree.blogspot.com

Bài 18: Khái niệm về Database

Flutter cung cấp một vài gói nâng cao để làm việc với
database(cơ sở dữ liệu). Nhưng 2 gói quan trọng nhất là :
1. sqflite - Sử dụng để truy vấn vào SQLlite database
2. firebase_database : Sử dụng để truy vấn và vận dụng
đám mây lưu trữ NoSQL database từ Google.
Trong chương này chúng ta sẽ bàn luận chi tiết về nó.
SQLite
SQLite là một SQL tiêu chuẩn dựa trên công cụ cơ sở dữ liệu
nhúng . Nó là công cụ nhỏ và đang được thử nghiệm theo thời
gian. Gói sqflite cung cấp nhiều chức năng để làm việc hiệu quả
với SQLite database. Nó cung cấp các phương thức tiêu chuẩn để
vận hành SQLite database. Chức năng chính của sqflite như sau :
1. Tạo/mở SQLite database
2. Thực thi SQL statement (thực thi phương thức) đối với
SQLite database
3. Phương thức truy vấn nâng cao (phương thức truy vấn)
để giảm code cần thiết để truy vấn và lấy thông tin
từ SQLite database.
Bây giờ ta thử tạo ứng dụng cửa hàng sản phẩm điện thoại và lấy
dữ liệu từ SQLite database sử dụng gói sqflite và hiểu các khải
niệm về SQLite database và gói sqflite.
Tạo một ứng dụng mới có tên là : product_sqlite_apptailieusharefree.blogspot.com

Copy thư mục assets bài 7 sang và thêm vào mục
*pubspec.yaml`
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
Cấu hình sqflite trong file pubspec.yaml như dưới đây
dependencies: sqflite: any
Sử dụng phiên bản mới nhất của sqflite điền vào chỗ any
Android studio sẽ thông báo rằng pubspec.yaml cần được cập
nhật rồi ta nhấn và Get dependencies. Android studio sẽ lấy
package từ internet và cấu hình thuộc tính cho ứng dụng
Trong database(cơ sở dữ liệu), chúng ta cần primary key, id như
trường bổ sung kèm với các thuộc tính của Product như : tên, giá,
... Vì thế, thêm thuộc tính id vào lớp Product. Ngoài ra, thêm 1
phương thức mới là toMap để chuyển đổi đối tượng product
vào Map.
class Product {
final int id;
final String name;
final String description;
final int price;
final String image;tailieusharefree.blogspot.com

static final columns = ["id", "name",
"description", "price", "image"];
Product(this.id, this.name, this.description,
this.price, this.image);
factory Product.fromMap(Map<String, dynamic>
data) {
return Product(
data['id'],
data['name'],
data['description'],
data['price'],
data['image'],
);
}
Map<String, dynamic> toMap() => {
"id": id,
"name": name,
"description": description,
"price": price,
"image": image
};
}
Tạo một file mới Database.dart để viết SQLite
Thêm một vài thư viện cần thiết trong Database.dart
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';tailieusharefree.blogspot.com

import 'package:sqflite/sqflite.dart';
import 'Product.dart';
Ta cần chú ý một vài điểm sau :
1. async được sử dụng để viết phương
thức asynchronous(không đồng bộ)
2. io được sử dụng để truy cập vào tệp và thư mục
3. path được sử dụng để truy cập vào tiện ích cốt lõi của
dart liên quan đến đường dẫn tệp
4. path_provider được sử dụng để lấy đường dẫn tạm
thời
5. sqflite được sử dụng để vận hành database SQLite
Tạo một lớp mới có tên SQLiteDbProvider
class SQLiteDbProvider {
SQLiteDbProvider._();
static final SQLiteDbProvider db =
SQLiteDbProvider._();
static Database _database;
}
SQLiteDBProvoider là phương thức có thể truy cập thông qua
biến đổi vùng nhớ db
SQLiteDBProvoider.db.<emthod>
Tạo hàm để lấy database (sử dụng Future) của kiểu
Future<Database>. Tạo bảng product và tải dữ liệu ban đầu trong
quá trình tạo database
Future<Database> get database async {
if (_database != null)
return _database;tailieusharefree.blogspot.com

_database = await initDB();
return _database;
}
initDB() async {
Directory documentsDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path,
"ProductDB.db");
return await openDatabase(
path,
version: 1,
onOpen: (db) {},
onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE Product ("
"id INTEGER PRIMARY KEY,"
"name TEXT,"
"description TEXT,"
"price INTEGER,"
"image TEXT" ")"
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[1, "iPhone", "iPhone is the stylist
phone ever", 1000, "iphone.png"]
); tailieusharefree.blogspot.com

await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[2, "Pixel", "Pixel is the most
feature phone ever", 800, "pixel.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[3, "Laptop", "Laptop is most
productive development tool", 2000, "laptop.png"]\
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[4, "Tablet", "Laptop is most
productive development tool", 1500, "tablet.png"]
);
await db.execute(
"INSERT INTO Product
('id', 'name', 'description', 'price',
'image')
values (?, ?, ?, ?, ?)",
[5, "Pendrive", "Pendrive is useful
storage medium", 100, "pendrive.png"]tailieusharefree.blogspot.com

);
await db.execute(
"INSERT INTO Product
('id', 'name', 'description', 'price',
'image')
values (?, ?, ?, ?, ?)",
[6, "Floppy Drive", "Floppy drive is
useful rescue storage medium", 20, "floppy.png"]
);
}
);
}
Chúng ta đã sử dụng một số phương thức như sau :
getApplicationDocumentsDirectory: trả về đường dẫn thư mục
ứng dụng
join : sử dụng để tạo đường dẫn cụ thể. Ta đã sử dụng nó để tạo
đường dẫn database
openDatabase : sử dụng để mở SQLite database
onOpen : Được dùng để viết code khi mở database
onCreate : Sử dụng để viết code khi database được tạo lần đầu
db.execute : Sử dụng để thực thi truy vấn SQL
Viết hàm để lấy tất cả product từ database
Future<List<Product>> getAllProducts() async {
final db = await database;
List<Map>
results = await db.query("Product", columns:
Product.columns, orderBy: "id ASC");tailieusharefree.blogspot.com

List<Product> products = new List();
results.forEach((result) {
Product product = Product.fromMap(result);
products.add(product);
});
return products;
}
Ở trên, chúng ta đã làm như sau :
1. Sử dụng phương thức truy vấn để lấy tất cả thông tin
của product. Truy vấn cung cấp lối tắt để truy cập vào
thông tin bảng mà không phải viết toàn bộ truy vấn .
Phương thức truy vấn sẽ tạo truy vấn chính nó bằng
việc sử dụng đầu vào như columns, orderBy , ...
2. Sử dụng phương thức Product’s fromMap để lấy chi
tiết product bằng việc chạy vòng lặp các đối tượng
Chúng ta viết hàm để lấy id cụ thể của product
Future<Product> getProductById(int id) async {
final db = await database;
var result = await db.query("Product", where:
"id = ", whereArgs: [id]);
return result.isNotEmpty ?
Product.fromMap(result.first) : Null;
}
Ta đã sử dụng where và whereArgs để áp dụng bộ lọc
Tạo 3 hàm - insert, update và delete để thêm, cập nhật và xoá
product từ db(database)tailieusharefree.blogspot.com

insert(Product product) async {
final db = await database;
var maxIdResult = await db.rawQuery(
"SELECT MAX(id)+1 as last_inserted_id FROM
Product");

var id = maxIdResult.first["last_inserted_id"];
var result = await db.rawInsert(
"INSERT Into Product (id, name, description,
price, image)"
" VALUES (?, ?, ?, ?, ?)",
[id, product.name, product.description,
product.price, product.image]
);
return result;
}
update(Product product) async {
final db = await database;
var result = await db.update("Product",
product.toMap(),
where: "id = ?", whereArgs: [product.id]);
return result;
}
delete(int id) async {
final db = await database;
db.delete("Product", where: "id = ?",
whereArgs: [id]);
}tailieusharefree.blogspot.com

Database.dart như sau :
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart';

class SQLiteDbProvider {
SQLiteDbProvider._();
static final SQLiteDbProvider db =
SQLiteDbProvider._();
static Database _database;

Future<Database> get database async {
if (_database != null)
return _database;
_database = await initDB();
return _database;
}
initDB() async {
Directory documentsDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path,
"ProductDB.db");
return await openDatabase(
path, version: 1,
onOpen: (db) {}, tailieusharefree.blogspot.com

onCreate: (Database db, int version)
async {
await db.execute(
"CREATE TABLE Product ("
"id INTEGER PRIMARY KEY,"
"name TEXT,"
"description TEXT,"
"price INTEGER,"
"image TEXT"")"
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[1, "iPhone", "iPhone is the
stylist phone ever", 1000, "iphone.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[2, "Pixel", "Pixel is the most
feature phone ever", 800, "pixel.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",tailieusharefree.blogspot.com

[3, "Laptop", "Laptop is most
productive development tool", 2000, "laptop.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[4, "Tablet", "Laptop is most
productive development tool", 1500, "tablet.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[5, "Pendrive", "Pendrive is useful
storage medium", 100, "pendrive.png"]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)",
[6, "Floppy Drive", "Floppy drive
is useful rescue storage medium", 20,
"floppy.png"]
);
}
);
}tailieusharefree.blogspot.com

Future<List<Product>> getAllProducts() async {
final db = await database;
List<Map> results = await db.query(
"Product", columns: Product.columns,
orderBy: "id ASC"
);
List<Product> products = new List();
results.forEach((result) {
Product product =
Product.fromMap(result);
products.add(product);
});
return products;
}
Future<Product> getProductById(int id) async {
final db = await database;
var result = await db.query("Product",
where: "id = ", whereArgs: [id]);
return result.isNotEmpty ?
Product.fromMap(result.first) : Null;
}
insert(Product product) async {
final db = await database;
var maxIdResult = await db.rawQuery("SELECT
MAX(id)+1 as last_inserted_id FROM Product");
var id =
maxIdResult.first["last_inserted_id"];
var result = await db.rawInsert(tailieusharefree.blogspot.com

"INSERT Into Product (id, name,
description, price, image)"
" VALUES (?, ?, ?, ?, ?)",
[id, product.name, product.description,
product.price, product.image]
);
return result;
}
update(Product product) async {
final db = await database;
var result = await db.update(
"Product", product.toMap(), where: "id =
?", whereArgs: [product.id]
);
return result;
}
delete(int id) async {
final db = await database;
db.delete("Product", where: "id = ?",
whereArgs: [id]);
}
}
Thay đổi trong hàm main để lấy thông tin product
void main() {
runApp(MyApp(products:
SQLiteDbProvider.db.getAllProducts()));
}
Ở đây ta đã dùng getAllProducts để lấy tất cả sản phẩm từ dbtailieusharefree.blogspot.com

Chạy ứng dụng lên và ta sẽ thấy được kết quả. Nó sẽ giống với
kết quả ở ví dụ trước "REST API"
C???} F??~????~
Firebase là nền tảng phát triển ứng dụng Baas(Backend-as-a-
Service). Nhiều lập trình viên trên thế giới đã sử dụng mBaaS bởi
vì nhiều lợi ích nó mang đến . Nó cung cấp nhiều tính năng để
tăng tốc việc phát triển ứng dụng như xác thực , lưu trữ đám mây
,... Một trong những tính năng chính của Firebase là Cloud
Firestore, là cloud dựa trên NoSQL database với thời gian thực
Flutter cung cấp gói cụ thể, cloud_firestore để làm việc với
Cloud Firebase . Chúng ta hãy tạo cửa hàng product trực tuyến
trên Cloud Firestore và tạo ứng dụng để truy cập vào .
1. Tạo ừng dụng flutter mới tên là product_firebase_app
2. Cooy tệp Product.dart từ thư mục product_rest_app
sang
class Product {
final String name;
final String description;
final int price;
final String image;

Product(this.name, this.description,
this.price, this.image);
factory Product.fromMap(Map<String, dynamic>
json) {tailieusharefree.blogspot.com

return Product(
json['name'],
json['description'],
json['price'],
json['image'],
);
}
}
Copy thư mục assets từ product_rest_app
vào product_firebase_app và thêm vào file pubspec.yaml
flutter:
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
Cấu hình gói cloud_firestore trong tệp pubspec.yaml như sau :
dependencies: cloud_firestore: ^0.9.13+1
Ta đang sử dụng phiên bản mới nhất của cloud_firestore (ở thời
điểm hiện tại)
Android studio sẽ thông báo cập nhật và Get dependencies nó.
Sau đó ta khởi tạo project Firebase theo từng bước sau đây :
1. Mở https://firebase.google.com/pricing/
2. Tạo một tài khoảng Firebase và tạo mới Project tailieusharefree.blogspot.com

3. Các bạn làm theo hướng dẫn chi tiết trong video này
nhé : https://www.youtube.com/watch?
v=6juww5Lmvgo
4. Lưu ý : bạn hãy nhớ kết nối project của mình với
firebase nhé
Oke, ta hãy tạo cửa hàng sản phẩm mới như sau :
1. Đi đến Firebase console
2. Mở project mình vừa tạo
3. Nhấn vào tính năng Database trong menu bên trái
4. Tạo database
5. Nhấn Start trong test mode và EnaEnable
6. Click Add collection.
Mở tệp main.dart và thêm plugin Cloud Firestore , xoá http
package
import
'package:cloud_firestore/cloud_firestore.dart';tailieusharefree.blogspot.com

Xoá parseProducts và cập nhật fetchProducts để lấy products
từ Cloud Firestore thay vì Product service API.
Stream<QuerySnapshot> fetchProducts() {
return
Firestore.instance.collection('product').snapshots
(); }
Ở đây, p[hương thức Firestore.instance.collection được sử dụng
để truy cập vào giá trị product trên cloud store.
Firestore.instance.collection cung cấp nhiều tính năng để lọc và
lấy tài liệu cần thiết. Nhưng chúng ta không áp dụng bất kì bộ lọc
nào để lấy toàn bộ thông tin product
Cloud Firestore cung cấp bộ sưu tập thông qua khái niệm Dart
Stream và sửa đổi kiểu dữ liệu products trong widget MyApp
và MyHomePage từ Future<List<Product>> sang
Stream<QuerySnapshot>
Thay đổi phương thức build của widget MyHomePage để sử
dụng StreamBuilder thay vì StreamBuilder
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product
Navigation")),
body: Center(
child: StreamBuilder<QuerySnapshot>(
stream: products, builder: (context,
snapshot) {tailieusharefree.blogspot.com

if (snapshot.hasError)
print(snapshot.error);
if(snapshot.hasData) {
List<DocumentSnapshot>
documents =
snapshot.data.documents;

List<Product>
items = List<Product>();

for(var i = 0; i <
documents.length; i++) {
DocumentSnapshot document =
documents[i];

items.add(Product.fromMap(document.data));
}
return ProductBoxList(items:
items);
} else {
return Center(child:
CircularProgressIndicator());
}
},
),
)
);
}tailieusharefree.blogspot.com

Ở trên, ta đã lấy được dữ liệu thông tin product như kiểu
List<DocumentSnapshot>.
Cuối cùng, ta chạy ứng dụng và xem kết quả . Chúng ta đã sử
dụng cùng thông tin product trong SQLite application và chỉ
thay đổi nơi lưu trữ vì thế ứng dụng sẽ hiển thị giống như SQLite
application
tailieusharefree.blogspot.com

Bài 19: Chuyển đổi ngôn ngữ

Ngày nay, các ứng dụng di động được sử dụng từ nhiều nơi trên
thế giới, vì thế các ứng dụng phải hiển thị nội dung phù hợp với
ngôn ngữ của quốc gia đó (ví dụ ngườ Pháp thì nội dung hiển thị
là tiếng Pháp, người Việt thì hiển thị tiếng Việt).Việc ứng dụng
làm việc với đa ngôn ngữ được gọi là Internationalizing(quốc tế
hoá).
Để ứng dụng làm việc với nhiều ngôn ngữ, đầu tiên nên tìm ngôn
ngữ hiện tại của hệ thống mà ứng dụng đang chạy và sau đó hiển
thị nội dung ở vị trí cụ thể và quy trình này được gọi
là Localization
Flutter framework cung cấp 3 lớp localization và các lớp tiện ích
có nguồn gốc từ các lớp dựa trên localize
Các lớp cơ sở như sau :
Locale - là lớp được sử dụng để nhận diện ngôn ngữ người sử
dụng. Ví dụ en-us nhận biết người Mỹ, người Anh và nó được
tạo ra như sau :
Locale en_locale = Locale('en', 'US')
Ở đây, đối số đầu tiên là mã ngôn ngữ, đối số thứ hai là mã quốc
gia. Một ví dụ khác và tạo Argentina Spanish (es-ar) như sau :
Locale es_locale = Locale('es', 'AR')
Localizations - là widget chung được sử dụng để set Locale và
nguồn localized của lớp con
class CustomLocalizations {tailieusharefree.blogspot.com

CustomLocalizations(this.locale);
final Locale locale;
static CustomLocalizations of(BuildContext
context) {
return Localizations.of<CustomLocalizations>
(context, CustomLocalizations);
}
static Map<String, Map<String, String>>
_resources = {
'en': {
'title': 'Demo',
'message': 'Hello World'
},
'es': {
'title': 'Manifestación',
'message': 'Hola Mundo',
},
};
String get title {
return _resources[locale.languageCode]
['title'];
}
String get message {
return _resources[locale.languageCode]
['message'];
}
}tailieusharefree.blogspot.com

Ở đây, CustomLocalizations là lớp custom mới được tạo riêng để
lấy nội dung localized nhất định (tiêu đề và thông báo) cho
widget,of của phương thức sử dụng lớp Localizations để trả về
lớp CustomLocalizations mới
LocalizationsDelegate<T> - LocalizationsDelegate<T> là lớp
factory thông qua widget Localizations được tải. Nó có 3 phương
thức over-ridable như sau :
* sSupported - Chấp nhận một miền - và trả về liệu miền đó có
được hỗ trợ hay không?
@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
Ở đây chỉ làm việc với en hoặc es
* load - Chấp nhận ngôn ngữ được chọn và bắt đầu tải các nguồn
dữ liệu của ngôn ngữ đó
@override
Future<CustomLocalizations> load(Locale locale) {
return SynchronousFuture<CustomLocalizations>
(CustomLocalizations(locale));
}
Ở trên, phương thức load trả về CustomLocalizations. việc trả
về CustomLocalizations có thể được sử dụng để lấy giá trị của
tiêu đề và thông báo của cả 2 ngôn ngữ Englist và Spanish
* shouldReload - Liệu có nên tải lại CustomLocalizations là cần
thiết khi widget Localizations được rebuild(giống như việc reset
lại trang)
@overridetailieusharefree.blogspot.com

bool shouldReload(CustomLocalizationsDelegate old)
=> false;
Code ở CustomLocalizationDelegate như sau :
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
const CustomLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
@override
Future<CustomLocalizations> load(Locale locale)
{
return
SynchronousFuture<CustomLocalizations>
(CustomLocalizations(locale));
}
@override bool
shouldReload(CustomLocalizationsDelegate old) =>
false;
}
Nhìn chung, ứng dụng Flutter dựa trên 2 cấp widget gốc
là MaterialApp và WidgetsApp. Flutter cung cấp miền cho cả 2
widget và nó là MaterialLocalizations và WidgetsLocaliations .
Thêm nữa , flutter cũng cung cấp quyền để
tải MaterialLocalizations và WidgetsLocaliations, đó
là GlobalMaterialLocalizations.delegate
và GlobalWidgetsLocalizations.delegate tương ứng. Chúng ta hãytailieusharefree.blogspot.com

tạo ứng dụng đa ngôn ngữ cơ bản để chạy thử và hiểu về nội
dung
Đầu tiên, ta tạo một ứng dụng mới với
tên flutter_localization_app
Flutter hỗ trợ đa ngôn ngữ(internationalization) sử dụng gói
flutter là flutter_localizations . Mở file pubspec.yaml và thêm
vào như sau :
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
Sau đó ta Get dependencies khi IDE hiện thông báo cập nhật
Thêm flutter_localizations vào main.dart:
import
'package:flutter_localizations/flutter_localizatio
ns.dart';
import 'package:flutter/foundation.dart' show
SynchronousFuture;
Ở đây, mục đích của SynchronousFuture để tải tuỳ
chỉnh localizations không đồng bộ
Tạo custom localizations như sau :
class CustomLocalizations {
CustomLocalizations(this.locale);
final Locale locale;
static CustomLocalizations of(BuildContext
context) {tailieusharefree.blogspot.com

return Localizations.of<CustomLocalizations>
(context, CustomLocalizations);
}
static Map<String, Map<String, String>>
_resources = {
'en': {
'title': 'Demo',
'message': 'Hello World'
},
'es': {
'title': 'Manifestación',
'message': 'Hola Mundo',
},
};
String get title {
return _resources[locale.languageCode]
['title'];
}
String get message {
return _resources[locale.languageCode]
['message'];
}
}
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
const CustomLocalizationsDelegate();

@override tailieusharefree.blogspot.com

bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);

@override
Future<CustomLocalizations> load(Locale locale)
{
return
SynchronousFuture<CustomLocalizations>
(CustomLocalizations(locale));
}
@override bool
shouldReload(CustomLocalizationsDelegate old) =>
false;
}
Ở trên, CustomLocalizations được tạo để hỗ trợ miền cho tiêu đề
và thông báo trong ứng dụng và CustomLocalizationsDelegate
được sử dụng để tải CustomLocalizations
Thêm quyền cho MaterialApp, WidgetsApp
và CustomLocalization sử dụng thuộc
tính MaterialApp, localizationsDelegates và supportedLocales như
sau :
localizationsDelegates: [
const CustomLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),tailieusharefree.blogspot.com

const Locale('es', ''),
],
Sử dụng phương thức CustomLocalizations , of để lấy giá trị
của localized và sử dụng nó thích hợp như sau :
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:
Text(CustomLocalizations .of(context) .title), ),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Text( CustomLocalizations
.of(context) .message, ),
],
),
),
);
}
}
Nhìn trên, ta đã sửa đổi lớp MyHomePage từ StatefulWidget
sang StatelessWidget và sử dụng CustomLocalizations để lấy tiêutailieusharefree.blogspot.com

đề, thông điệp
Biên dịch và chạy ứng dụng . Ứng dụng sẽ hiển thị nội dung là
tiếng Anh.
Đóng ứng dụng. Vào Settings → System → Languages and
Input → Languages*
Nhấn thêm ngôn ngữ và lựa chọn Spanish. Điện thoại sẽ cài đặt
ngôn ngữ Spanish
Lựa chọn Spanish và di chuyển nó lên trên English. Nó sẽ mặc
định chọn ngôn ngữ Spanish là ngôn ngữ đầu tiên và tất cả sẽ
được chuyển sang ngôn ngữ Spanish
Chúng ta có thể thay đổi lại ngôn ngữ tiến Anh bằng cách tương
tự là di chuyển English lên trên đầu trong cài đặt
Và kết quả hiển thị như sau :tailieusharefree.blogspot.com

Sử }ụ?? ??? ????
Flutter cung cấp intl package để đơn giản việc phát triển localized
trong ứng dụng mobile.intl package cung cấp phương thức đặc
biệt và công cụ để tạo bán tự động ngôn ngữ thông điệp cụ thể
Chúng ta sẽ tạo ứng dụng localized mới bằng việc sử dụng intl
package và hiểu về ý tưởng của package này
- Tạo ứng dụng flutter mới với tên "flutter_intl_app"
- Mở file pubspec.yaml và thêm package như sau :
dependencies:
flutter:
sdk: fluttertailieusharefree.blogspot.com

flutter_localizations:
sdk: flutter
intl: ^0.15.7
intl_translation: ^0.17.3
Get dependencies khi Android studio hiển thị thông báo câp nhật
thay đổi
- Copy hàm main.dart trong ví dụ
trước flutter_internationalization_app
- Import intl package như sau
import 'package:intl/intl.dart';
Cập nhật lớp CustomLocalization :
class CustomLocalizations {
static Future<CustomLocalizations> load(Locale
locale) {
final String name =
locale.countryCode.isEmpty ? locale.languageCode :
locale.toString();
final String localeName =
Intl.canonicalizedLocale(name);

return
initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
return CustomLocalizations();
});
}
static CustomLocalizations of(BuildContext
context) {tailieusharefree.blogspot.com

return Localizations.of<CustomLocalizations>
(context, CustomLocalizations);
}
String get title {
return Intl.message(
'Demo',
name: 'title',
desc: 'Title for the Demo application',
);
}
String get message{
return Intl.message(
'Hello World',
name: 'message',
desc: 'Message for the Demo application',
);
}
}
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
const CustomLocalizationsDelegate();

@override
bool isSupported(Locale locale) => ['en',
'es'].contains(locale.languageCode);
@override
Future<CustomLocalizations> load(Locale locale)
{ tailieusharefree.blogspot.com

return CustomLocalizations.load(locale);
}
@override
bool shouldReload(CustomLocalizationsDelegate
old) => false;
}
Ở trên, ta đã sử dụng 3 phương thức từ intl package thay vì
phương thức tự tạo .
1. Intl.canonicalizedLocale - được sử dụng để lấy chính
xác tên ngôn ngữ
2. Intl.defaultLocale - Sử dụng để set ngôn ngữ hiện tại
3. Intl.message - Sử dụng để định nghĩa thông điệp mới
import l10n/messages_all.dart file
import 'l10n/messages_all.dart';
Tiếp theo, ta tạo thư mục lib/l10n
Mở command prompt và đi đến ứng dụng thư mục gốc
(pubspec.yaml) và chạy đoạn command sau :
flutter packages pub run
intl_translation:extract_to_arb --output-
dir=lib/l10n lib/main.dart
Ở đây, lệnh được tạo intl_message.arb file, Một bản mẫu để tạo
thông điệp trong ngôn ngữ khác . Nội dung của file như sau :
{
"@@last_modified": "2019-04-
19T02:04:09.627551",
"title": "Demo",
"@title": {tailieusharefree.blogspot.com

"description": "Title for the Demo
application",
"type": "text",
"placeholders": {}
},
"message": "Hello World",
"@message": {
"description": "Message for the Demo
application",
"type": "text",
"placeholders": {}
}
}
Copy intl_message.arb và tạo file mới, intl_en.arb và thay đổi nội
dung sang ngôn ngữ Spanish :
{
"@@last_modified": "2019-04-
19T02:04:09.627551",
"title": "Manifestación",
"@title": {
"description": "Title for the Demo
application",
"type": "text",
"placeholders": {}
},
"message": "Hola Mundo",
"@message": {tailieusharefree.blogspot.com

"description": "Message for the Demo
application",
"type": "text",
"placeholders": {}
}
}
Oke, bây giờ ta thử run lệnh để tạo tệp cuối
cùng, messages_all.dart.
flutter packages pub run
intl_translation:generate_from_arb
--output-dir=lib\l10n --no-use-deferred-loading
lib\main.dart lib\l10n\intl_en.arb
lib\l10n\intl_es.arb
Biên dịch và run app .Nó sẽ hoạt động như ứng
dụng flutter_localization_app ở trên
tailieusharefree.blogspot.com

Bài 20: Testing
Testing là một phần rất quan trọng trong việc phát triển vòng đời
của một ứng dụng. Nó đảm bảo rằng ứng dụng sẽ tốt hơn và chất
lượng cao hơn.Testing yêu cầu có kế hoạch và thực thi cẩn thận .
Nó cũng tiêu tốn nhiều thời gian nhất trong việc lập trình
Dart và Flutter framework cung cấp gói mở rộng để hỗ trợ trong
việc testing tự động của ứng dụng
Mộ? ?ố }ạ?? T~?????
Thông thường, có 3 loại testing
Unit Testing
Là phương pháp testing đơn giản nhất . Nó dựa trên việc đảm bảo
độ chính xác của một đoạn code . Nhưng nó hoạt động không
thực sự tốt trên môi trường thật nên nó ít được sử dụng trong việc
tìm lỗi
Widget Testing
Được dựa trên việc đảm bảo độ chính xác trong việc tạo, render
hay tương tác của widget với widget khác như mong đợi. Nó hoạt
động từng bước và cung cấp gần như thời gian thực trong việc
tìm lỗi
Integration Testing
Integration testing bao gồm cả hai unit testing và widget testing
cùng với các thành phần bên ngoài ứng dụng như database, web
service, .. Nó mô phỏng hoặc giả lập môi trường thực để tìm ratailieusharefree.blogspot.com

gần như tất cả các lỗi . Vì thế nó là quá trình phức tạp nhất của
ưng dụng
Flutter cung cấp, hỗ trợ tất cả các loại testing. Nó cung cấp gói
mở rộng và hỗ trợ riêng cho Widget testing. Trong bài này, chúng
ta sẽ tập trung bàn luận chi tiết về widget testing
W?}?~? T~?????
Flutter testing framework cung cấp phương thúc testWidgets để
test widgets. Nó chấp nhận 2 tham số
- Test decription
- Test code
testWidgets('test description: find a widget',
'<test code>')
Các bước thực hiện
Widget Testing thực hiện 3 bước khác biệt như sau :
- Render widget trong môi trường testing
- WidgetTester là lớp cung cấp bởi Flutter testing framework để
build và render widget. Phương thức pumpWidget của
lớp WidgetTester chấp nhận bất kì Widget và render nó trong môi
trường testing
testWidgets('finds a specific instance',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Text('Hello'),
), tailieusharefree.blogspot.com

));
});
Tìm đến widget mà chúng ta cần test
- Flutter framework cung cấp nhiều tính năng để tìm đến widget
render trong môi trường testing và gọi chung là Finders.Chúng ta
hầu như thường xuyên sử dụng sử dụng finders là find.text,
find.byKey và find.byWidget
1. find.text để tìm widget mà chứa đoạn text cụ thể -
find.text('Hello')
2. find.byKey để tìm widget chứa các key cụ thể -
find.byKey('home')
3. find.byWidget để tìm wiget theo biến thể của nó -
find.byWidget (homeWidget)
- Đảm bảo các widget làm việc như mong đợi
- Flutter framework cung cấp nhiều tính năng để phù hợp widget
với widget dự kiến và gọi là Matchers. Một vài điều quan trọng
về matchers như sau :
findsOneWidget - Xác minh widget duy nhất được tìm thấy
expect(find.text('Hello'), findsOneWidget);
findsNothing - Xác minh không wiget nào được tìm thấy
expect(find.text('Hello World'), findsNothing);
findsWidgets − Xác minh nhiều hơn một wiget được tìm thấy
expect(find.text('Save'), findsWidgets);
findsNWidgets - Xác minh N widget tìm thấy
expect(find.text('Save'), findsNWidgets(2));
Đoạn code ví dụ như sau :tailieusharefree.blogspot.com

testWidgets('finds hello widget', (WidgetTester
tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Text('Hello'),
),
));
expect(find.text('Hello'), findsOneWidget);
});
Ở trên, chúng ta render một widget MaterialApp tới dòng
text Hello sử dụng Text widget. Sau đó , ta sử dụng find.text để
tìm widget và khớp nó bằng cách sử dụng findsOneWidget
V? }ụ |ụ ??ể
Ta sẽ tạo nhanh ứng dụng flutter và viết widget test để hiểu hơn
các bước về ý tưởng với tên flutter_test_app
Mở widget_test.dart trong thư mục .
testWidgets('Counter increments smoke test',
(WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());

// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);

// Tap the '+' icon and trigger a frame.tailieusharefree.blogspot.com

await tester.tap(find.byIcon(Icons.add));
await tester.pump();

// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
Ở đây, test code có chức năng như sau :
- Renders MyApp widget sử dụng tester.pumpWidget
- Đảm bảo rằng bộ đếm ban đầu bằng 0 sử dụng findsOneWidget
và findsOneWidget
- Tìm nút tăng bộ đếm sử dụng phương thức tester.tap
- Đảm bảo rằng bộ đếm được tăng lên bằng cách sử
dụng findsOneWidget và findsNothing
Ta hãy thử nhấn vào nút tăng bộ đếm và sau đó kiểm tra liệu bộ
đếm có tăng lên là 2
await tester.tap(find.byIcon(Icons.add));
await tester.pump();

expect(find.text('2'), findsOneWidget);
Nhấn Run menu
Nhấn test trong widget_test.dart . Nó sẽ chạy và hiển thị kết quả
trong cửa sổ kết quả
tailieusharefree.blogspot.com

Bài 21: Xuất ứng dụng trong
Flutter

Trong bài này mình sẽ giải thích làm thế nào để triển khai ứng
dụng Flutter trên 2 nền tảng IOS và Android
A?}???}
- Thay đổi tên ứng dụng bằng cách truy cập vào lệnh
android:label trong file manifest. AndroidManifest,xml được đặt
trong <app dir>.android/app/src/main. Trong file này chứa toàn
bộ chi tiết về ứng dụng Android. Chúng ta có thể đặt tên ứng
dụng thông qua android:label
- Thay đổi icon của ứng dụng bằng android:icon trong file
manifest
- Cách xuất ứng dụng sang file APK bằng lệnh sau :
cd /path/to/my/application
flutter build apk
Sau khi chạy dòng lệnh thì màn hình sẽ hiển thị như sau :
Initializing gradle...
8.6s
Resolving dependencies...
19.9s
Calling mockable JAR artifact transform to create
file:tailieusharefree.blogspot.com

/Users/.gradle/caches/transforms-1/files-
1.1/android.jar/
c30932f130afbf3fd90c131ef9069a0b/android.jar with
input
/Users/Library/Android/sdk/platforms/android-
28/android.jar
Running Gradle task 'assembleRelease'...
Running Gradle task 'assembleRelease'...
Done
85.7s
Built build/app/outputs/apk/release/app-
release.apk (4.8MB).
- Cách cài đặt file APK trực tiếp trên thiết bị di động:
flutter install
`- Đẩy ứng dụng lên Google PlayStore bằng cách tạo appbundle
và đẩy nó lên bằng lệnh sau :
flutter build appbundle
IOS
- Đầu tiên ta cần đăng kí tài khoản App Store Connect..Lưu ý
lưu =Bundle ID đã đăng kí để sau này khi update ứng dụng cần
tới
- Cập nhật tên Display trong phần cài đặt project của XCode để
đặt tên ứng dụng
- Cập nhật Bundle Identifier trong cài đặt của project Xcode để
đặt bundle id mà ta sử dụng ở bước 1
- Thêm icon mới tailieusharefree.blogspot.com

- Tạo file IPA sử dụng lệnh sau
flutter build ios
- Và đây là output
Building com.example.MyApp for device (ios-
release)...
Automatically signing iOS for device deployment
using specified development team in Xcode project:
Running Xcode build...
23.5s
......................
tailieusharefree.blogspot.com

Bài 22: Công cụ phát triển

Hôm nay mình sẽ giải thích chi tiết về công cụ phát triển trong
Flutter . Bộ công cụ phát triển đa nền tảng đầu tiên được phát
hành ngày 4/12/22018. Google đã tiếp tục làm việc để cả thiện và
phát triển mạnh mẽ flutter framework với các công cụ khác nhau
W?}?~? S~??
Google cập nhật widget Material và Cupertino để cung cấp chất
lượng độ phân giải tốt trong thành phần thiết kế. Bản mới nhất là
flutter 1.2 được thiết kế để hỗ trợ lắng nghe sự kiện từ bàn phím
và chuột
P??? ???ể? ứ?? }ụ?? F????~? ?ớ? V???z? S??}??
C?}~
Visual Studio Code hỗ trợ trong việc phát triển ứng dụng flutter
và cung cấp các phím tắt mở rộng để phát triển một cách nhanh
chóng và đạt hiệu quả cao . Một vài tính năng chính được cung
cấp bởi Visual Studio Code như sau :
1. Hỗ trợ code : Khi chúng ta muốn kiểm tra một tính
năng , ta chỉ cần nhấn Ctrl+Space thì sẽ hiển thị một
list các tính năng phù hợp
2. Ctrl+ : công cụ sửa lỗi nhanh
3. Phím tắt khi codingtailieusharefree.blogspot.com

4. Cung cấp chi tiết chức năng và cách dùng trong
comments
5. Phím tắt Debugging
6. Hot restarts
Dz?? D~?T????
Ta có thể hoàn toàn sử dụng Android Studio hay Visual Studio
Code , hoặc bất kì IDE khác để viết code và cài đặt plugins. Đội
ngũ phát triển của Google đã và đang làm việc với các công cụ
phát triển khác gọi Dart DevTools . Đó là bộ lập trình dựa trên
web. Nó hỗ trợ cả hai nền tảng là Android và IOS.
C" Dặ DT
Để cài đặt Devtools thì ta cần nhấn lệnh sau :
flutter packages pub global activate devtools
Sau đó ở command line sẽ hiển thị :
Resolving dependencies...
+ args 1.5.1
+ async 2.2.0
+ charcode 1.1.2
+ codemirror 0.5.3+5.44.0
+ collection 1.14.11
+ convert 2.1.1
+ devtools 0.0.16
+ devtools_server 0.0.2
+ http 0.12.0+2
+ http_parser 3.1.3tailieusharefree.blogspot.com

+ intl 0.15.8
+ js 0.6.1+1
+ meta 1.1.7
+ mime 0.9.6+2
..................
..................
Installed executable devtools.
Activated devtools 0.0.16.
Cạ S
Bạn có thể chạy máy chủ DevTools như sau :
flutter packages pub global run devtools
Máy chủ sẽ phản hồi
Serving DevTools at http://127.0.0.1:9100
Bắ Dầ ớ ứ ụ ủ ạ
Đi đến ứng dụng của bạn, mở giả lập và chạy với dòng lệnh :
flutter run --observatory-port=9200
Và bây giờ ta đã kết nối được với DevTools
Bắ Dầ ớ DT B
Truy cập địa chỉ sau để kết nối với DevTools
http://localhost:9100/?port=9200
Trên trình duyệt của bạn sẽ hiển thị như sau :tailieusharefree.blogspot.com

F????~? SDK
Để cập nhật Flutter SDK , ta sử dụng dòng lệnh sau :
flutter upgrade
Bạn có thể thấy kết quả như sau :
Để cập nhật các package trong flutter, ta dùng lệnh
flutter packages upgrade
Bạn sẽ thấy dòng phản hồi
Running "flutter packages upgrade" in my_app...
7.4s
F I
Nó được sử dụng để khám phá widget flutter ở dạng cây . Để làm
được điều này, chạy dòng lệnh sau ở console
flutter run --track-widget-creation
Bạn có thể thấy kết quả như sau :tailieusharefree.blogspot.com

Launching lib/main.dart on iPhone X in debug
mode...
─Assembling Flutter resources...
3.6s
Compiling, linking and signing...
6.8s
Xcode build done.
14.2s
2,904ms (!)
To hot reload changes while running, press "r". To
hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone X
is available at: http://127.0.0.1:50399/
For a more detailed help message, press "h". To
detach, press "d"; to quit, press "q".
Tới url ở trên http://127.0.0.1:50399/, ta sẽ được kết quả như sau :
tailieusharefree.blogspot.com

Bài 23: Viết ứng dụng hoàn chỉnh

Trong bài này, chúng ta sẽ tạo một ứng dụng hoàn chỉnh
là expense_calculator. Mục đích của ứng dụng là để lưu trữ thông
tin chi phí. Để hoàn thành ứng dụng ta cần các tính năng sau đây
:
1. Danh sách chi phí
2. Tạo mẫu để nhập chi phí mới
3. Tính năng chỉnh sửa/xoá
4. Tính tổng chi phí trong bất kì trường hợp nào
Ta sẽ làm việc với ứng dụng và sử dụng một vào tính năng tiên
tiến của flutter framework
1. Sử dụng listview để hiển thị ra danh sách chi phí
2. Lập trình mẫu
3. Sử dụng SQLite để lưu trữ chi phí
4. Sử dụng quản lý vùng nhớ scoped_model để đơn giản
hoá việc lập trình
Nào, chúng ta hãy bắt đầu với ứng dụng expense_calculator
- Tạo một ứng dụng flutter với tên expense_calculator trên
android studio hoặc visual studio code tuỳ ý
- Mở file pubspec.yaml và thêm một vài package sau :
dependencies:
flutter:
sdk: flutter
sqflite: ^1.1.0tailieusharefree.blogspot.com

path_provider: ^0.5.0+1
scoped_model: ^1.0.1
intl: any
-Lưu ý một vài điểm sau :
sqflite được dùng cho SQLite db
path_provider được sử dụng để lấy đường dẫn hệ thống
riêng của ứng dụng
scoped_model dùng để quản lý vùng nhớ
intl dùng để định dạng ngày
-Sau đó ta sẽ nhấn Get dependencies để cập nhật lại các package
trong ứng dụng
-Xoá main.dart
-Tạo file mới là Expense.dart để tạo lớp Expense. Lớp Expense
bao gồm các thuộc tính và phương thức như sau
property: id - mỗi chi phí được đưa vào SQLite db đều
có một địa chỉ riêng và dùng id để phân biệt
property: amount - số lượng chi tiêu
property: date - thời gian chi tiêu
property: category - Dùng để phân biệt các đối tượng
đã chi tiêu như : Food, Travel, ..
formattedDate - định dạng lại ngày
fromMap - Dùng để map các trường trong db với các
thuộc tính đối tượng expense và tạo ra đối
tượng expense mới
factory Expense.fromMap(Map<String, dynamic> data)
{tailieusharefree.blogspot.com

return Expense(
data['id'],
data['amount'],
DateTime.parse(data['date']),
data['category']
);
}
toMap - Sử dụng để chuyển đổi tôi tượng expense
sang dart map mà xa hơn là sử dung để lập trình db
Map<String, dynamic> toMap() => {
"id" : id,
"amount" : amount,
"date" : date.toString(),
"category" : category,
};
columns - đại diện cho một trường của db
- Truy cập và lưu đoạn code này vào Expense.dart:
import 'package:intl/intl.dart'; class Expense {
final int id;
final double amount;
final DateTime date;
final String category;
String get formattedDate {
var formatter = new DateFormat('yyyy-MM-
dd');
return formatter.format(this.date);
} tailieusharefree.blogspot.com

static final columns = ['id', 'amount', 'date',
'category'];
Expense(this.id, this.amount, this.date,
this.category);
factory Expense.fromMap(Map<String, dynamic>
data) {
return Expense(
data['id'],
data['amount'],
DateTime.parse(data['date']),
data['category']
);
}
Map<String, dynamic> toMap() => {
"id" : id,
"amount" : amount,
"date" : date.toString(),
"category" : category,
};
}
- Thêm file mới có tên là Database.dart để tạo
lớp SQLiteDbProvider. Mục đích của SQLiteDbProvider là :
Lấy toàn bộ giá trị expenses trong db bằng phương
thức getAllExpenses. Phương thức này cho phép sử
dụng list tất cả thông tin user expenses
Future<List<Expense>> getAllExpenses() async {
final db = await database;
tailieusharefree.blogspot.com

List<Map> results = await db.query(
"Expense", columns: Expense.columns,
orderBy: "date DESC"
);
List<Expense> expenses = new List();
results.forEach((result) {
Expense expense = Expense.fromMap(result);
expenses.add(expense);
});
return expenses;
}
Lấy ra thông tin expense cụ thể dựa trên expense có sẵn
trong db bằng phương thức getExpenseById. Dùng để
hiển thị thông tin cụ thể của một expense
Future<Expense> getExpenseById(int id) async {
final db = await database;
var result = await db.query("Expense", where:
"id = ", whereArgs: [id]);

return result.isNotEmpty ?
Expense.fromMap(result.first) : Null;
}
Lấy toàn bộ expenses của user bằng phương
thức getTotalExpense. Dùng để hiển thị toàn bộ user ở
thời điểm đó
Future<double> getTotalExpense() async {
final db = await database;
List<Map> list = await db.rawQuery(tailieusharefree.blogspot.com

"Select SUM(amount) as amount from expense"
);
return list.isNotEmpty ? list[0]["amount"] :
Null;
}
Thêm một thông tin expense mới bằng phương
thức insert . Có chức năng thêm một user mới trong
Future<Expense> insert(Expense expense) async {
final db = await database;
var maxIdResult = await db.rawQuery(
"SELECT MAX(id)+1 as last_inserted_id FROM
Expense"
);
var id = maxIdResult.first["last_inserted_id"];
var result = await db.rawInsert(
"INSERT Into Expense (id, amount, date,
category)"
" VALUES (?, ?, ?, ?)", [
id, expense.amount,
expense.date.toString(), expense.category
]
);
return Expense(id, expense.amount,
expense.date, expense.category);
}
Cập nhật thông tin của user expense bằng phương
thức update . Ta có thể chỉnh sửa hoặc cập nhật giá trị
có sẵn của expensetailieusharefree.blogspot.com

update(Expense product) async {
final db = await database;

var result = await db.update("Expense",
product.toMap(),
where: "id = ?", whereArgs: [product.id]);
return result;
}
Xoá thông tin user có sẵn trong db bằng phương
thức delete .
delete(int id) async {
final db = await database;
db.delete("Expense", where: "id = ?",
whereArgs: [id]);
}
- Toàn bộ code trong SQLiteDbProvider như sau :
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Expense.dart';

class SQLiteDbProvider {
SQLiteDbProvider._();
static final SQLiteDbProvider db =
SQLiteDbProvider._();
tailieusharefree.blogspot.com

static Database _database; Future<Database> get
database async {
if (_database != null)
return _database;
_database = await initDB();
return _database;
}
initDB() async {
Directory documentsDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path,
"ExpenseDB2.db");
return await openDatabase(
path, version: 1, onOpen:(db){},
onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE Expense (
""id INTEGER PRIMARY KEY,"
"amount REAL," "date TEXT," "category TEXT""
)
");
await db.execute(
"INSERT INTO Expense ('id',
'amount', 'date', 'category')
values (?, ?, ?, ?)",[1, 1000,
'2019-04-01 10:00:00', "Food"]
);
/*await db.execute(tailieusharefree.blogspot.com

"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
2, "Pixel", "Pixel is the most
feature phone ever", 800, "pixel.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
3, "Laptop", "Laptop is most
productive development tool", 2000, "laptop.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
4, "Tablet", "Laptop is most
productive development tool", 1500, "tablet.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [tailieusharefree.blogspot.com

5, "Pendrive", "iPhone is the
stylist phone ever", 100, "pendrive.png"
]
);
await db.execute(
"INSERT INTO Product ('id', 'name',
'description', 'price', 'image')
values (?, ?, ?, ?, ?)", [
6, "Floppy Drive", "iPhone is
the stylist phone ever", 20, "floppy.png"
]
); */
}
);
}
Future<List<Expense>> getAllExpenses() async {
final db = await database;
List<Map>
results = await db.query(
"Expense", columns: Expense.columns,
orderBy: "date DESC"
);
List<Expense> expenses = new List();
results.forEach((result) {
Expense expense =
Expense.fromMap(result);
expenses.add(expense);
}); tailieusharefree.blogspot.com

return expenses;
}
Future<Expense> getExpenseById(int id) async {
final db = await database;
var result = await db.query("Expense",
where: "id = ", whereArgs: [id]);
return result.isNotEmpty ?
Expense.fromMap(result.first) : Null;
}
Future<double> getTotalExpense() async {
final db = await database;
List<Map> list = await db.rawQuery(
"Select SUM(amount) as amount from
expense"
);
return list.isNotEmpty ? list[0]["amount"] :
Null;
}
Future<Expense> insert(Expense expense) async {
final db = await database;
var maxIdResult = await db.rawQuery(
"SELECT MAX(id)+1 as last_inserted_id
FROM Expense"
);
var id =
maxIdResult.first["last_inserted_id"];
var result = await db.rawInsert(tailieusharefree.blogspot.com

"INSERT Into Expense (id, amount, date,
category)"
" VALUES (?, ?, ?, ?)", [
id, expense.amount,
expense.date.toString(), expense.category
]
);
return Expense(id, expense.amount,
expense.date, expense.category);
}
update(Expense product) async {
final db = await database;
var result = await db.update(
"Expense", product.toMap(), where: "id =
?", whereArgs: [product.id]
);
return result;
}
delete(int id) async {
final db = await database;
db.delete("Expense", where: "id = ?",
whereArgs: [id]);
}
}
- Ở đây ta giải thích như sau:
database là thuộc tính để lấy đối
tượng SQLiteDbProvidertailieusharefree.blogspot.com

initDB là phương thức được sử dụng để chọn và
mở SQLite db
- Tạo một file mới tên là ExpenseListModel.dart dùng để
tạo ExpenseListModel, Mục đích của việc tạo modell này dùng để
lưu giữ thông tin đầy đủ của user expense trong bộ nhớ và cập
nhật giao diện user trên ứng dụng bất cứ khi nào ta thay đổi user
expense trong bộ nhớ. Nó hoạt động dựa vào lớp Model từ
package scoped_model . Nó có thuộc tính và phương thức như
sau :
_items : list expense ở dạng private (không truy cập trực
tiếp vào biến này được)
items - getter từ _items
như UnmodifiableListView<expense> để ngăn chặn
thay đổi bất ngờ hoặc vô tình vào list
totalExpense - getter Total expenses dựa trên các item
có sẵn
double get totalExpense {
double amount = 0.0;
for(var i = 0; i < _items.length; i++) {
amount = amount + _items[i].amount;
}
return amount;
}
load - Dùng để tải expense hoàn chỉnh từ db thông
qua _items. Nó gọi phương thức notifyListeners để cập
nhật UItailieusharefree.blogspot.com

void load() {
Future<List<Expense>>
list = SQLiteDbProvider.db.getAllExpenses();
list.then( (dbItems) {
for(var i = 0; i < dbItems.length; i++) {
_items.add(dbItems[i]);
} notifyListeners();
});
}
byId - sử dụng để lấy expenses cụ thể từ _items
Expense byId(int id) {
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == id) {
return _items[i];
}
}
return null;
}
add - Sử dụng để thêm item expense mới thông
qua _items vào db. Nó cũng gọi notifyListeners để cập
nhật UI
void add(Expense item) {
SQLiteDbProvider.db.insert(item).then((val) {
_items.add(val); notifyListeners();
});
}
Update - Sử dụng để cập nhật item expense thông
qua _items . Và gọi phương thức notifyListeners để cậptailieusharefree.blogspot.com

nhật UI
void update(Expense item) {
bool found = false;
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == item.id) {
_items[i] = item;
found = true;
SQLiteDbProvider.db.update(item); break;
}
}
if(found) notifyListeners();
}
delete - Sử dụng để xoá item expense thông qua
biến _items từ db. Gọi notifyListeners để cập nhật UI
void delete(Expense item) {
bool found = false;
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == item.id) {
found = true;
SQLiteDbProvider.db.delete(item.id);
_items.removeAt(i); break;
}
}
if(found) notifyListeners();
}
- Toàn bộ code trong lớp ExpenseListModel :
import 'dart:collection';
import 'package:scoped_model/scoped_model.dart';tailieusharefree.blogspot.com

import 'Expense.dart';
import 'Database.dart';

class ExpenseListModel extends Model {
ExpenseListModel() {
this.load();
}
final List<Expense> _items = [];
UnmodifiableListView<Expense> get items =>
UnmodifiableListView(_items);

/*Future<double> get totalExpense {
return
SQLiteDbProvider.db.getTotalExpense();
}*/

double get totalExpense {
double amount = 0.0;
for(var i = 0; i < _items.length; i++) {
amount = amount + _items[i].amount;
}
return amount;
}
void load() {
Future<List<Expense>> list =
SQLiteDbProvider.db.getAllExpenses();
list.then( (dbItems) {
for(var i = 0; i < dbItems.length; i++) {tailieusharefree.blogspot.com

_items.add(dbItems[i]);
}
notifyListeners();
});
}
Expense byId(int id) {
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == id) {
return _items[i];
}
}
return null;
}
void add(Expense item) {
SQLiteDbProvider.db.insert(item).then((val)
{
_items.add(val);
notifyListeners();
});
}
void update(Expense item) {
bool found = false;
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == item.id) {
_items[i] = item;
found = true;
SQLiteDbProvider.db.update(item);
break;tailieusharefree.blogspot.com

}
}
if(found) notifyListeners();
}
void delete(Expense item) {
bool found = false;
for(var i = 0; i < _items.length; i++) {
if(_items[i].id == item.id) {
found = true;
SQLiteDbProvider.db.delete(item.id);
_items.removeAt(i); break;
}
}
if(found) notifyListeners();
}
}
- Mở file main.dart, import một vài lớp như sau :
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';
- Thêm hàm main và gọi runApp để đi tới widget
ScopedModel<ExpenseListModel>
void main() {
final expenses = ExpenseListModel();
runApp(
ScopedModel<ExpenseListModel>(model:
expenses, child: MyApp(),)tailieusharefree.blogspot.com

);
}
- Ở đây ta giải thích như sau
expenses sẽ được tải toàn bộ dữ liệu thông tin của các
user từ db. Ngoài ra , khi ứng dụng được mở lần đầu,
nó sẽ tự động tạo ra db cần thiết với thuộc tính bảng
ScopedModel cung cấp thông tin expense trong toàn bộ
vòng đời của ứng dụng và đảm bảo vùng nhớ của ứng
dụng trong bất kì trường hợp nào. Ta sẽ sử
dụng StatelessWidget thay vì StatefullWidget
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expense',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Expense
calculator'),
);
}
}
- Tạo widget MyHomePage để hiển thị toàn bộ thông tin user
expense
class MyHomePage extends StatelessWidget {tailieusharefree.blogspot.com

MyHomePage({Key key, this.title}) : super(key:
key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body:
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return ListView.separated(
itemCount: expenses.items ==
null ? 1
: expenses.items.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return ListTile(
title: Text("Total
expenses: "
+
expenses.totalExpense.toString(),
style:
TextStyle(fontSize: 24,
fontWeight:
FontWeight.bold),)
);tailieusharefree.blogspot.com

} else {
index = index - 1;
return Dismissible(
key:
Key(expenses.items[index].id.toString()),
onDismissed:
(direction) {

expenses.delete(expenses.items[index]);

Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
"Item with
id, "
+
expenses.items[index].id.toString() +
" is
dismissed"
)
)
);
},
child: ListTile( onTap:
() {
Navigator.push(
context,
MaterialPageRoute(tailieusharefree.blogspot.com

builder:
(context) => FormPage(
id:
expenses.items[index].id,
expenses:
expenses,
)
)
);
},
leading:
Icon(Icons.monetization_on),
trailing:
Icon(Icons.keyboard_arrow_right),
title:
Text(expenses.items[index].category + ": " +

expenses.items[index].amount.toString() +
" \nspent on " +
expenses.items[index].formattedDate,
style:
TextStyle(fontSize: 18, fontStyle:
FontStyle.italic),))
);
}
},
separatorBuilder: (context,
index) {tailieusharefree.blogspot.com

return Divider();
},
);
},
),
floatingActionButton:
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) =>
ScopedModelDescendant<ExpenseListModel>(
builder: (context,
child, expenses) {
return FormPage( id:
0, expenses: expenses, );
}
)
)
);
// expenses.add(new Expense(
// 2, 1000,
DateTime.parse('2019-04-01 11:00:00'), 'Food')
);
// print(expenses.items.length);
},tailieusharefree.blogspot.com

tooltip: 'Increment', child:
Icon(Icons.add), );
}
)
);
}
}
- Ta giải thích một vài điểm chính về đoạn code trên :
ScopedModelDescendant được sử dụng để chuyển đến
model expense trong Listview và
widget FloatingActionButton
ListView.separated và widget ListTile được sử dụng để
hiển thị thộng tin expense
widget Dismissible dùng để xoá bằng cử chỉ vuốt màn
hình
Navigator được sử dụng để mở giao diện . Nó có thể
được kích hoạt bằng động tác chạm
Tạo widget FormPage . Mục đích của widget FormPage là thêm
và cập nhật một expense
class FormPage extends StatefulWidget {
FormPage({Key key, this.id, this.expenses}) :
super(key: key);
final int id;
final ExpenseListModel expenses;

@override _FormPageState createState() =>
_FormPageState(id: id, expenses: expenses); tailieusharefree.blogspot.com

}
class _FormPageState extends State<FormPage> {
_FormPageState({Key key, this.id,
this.expenses});

final int id;
final ExpenseListModel expenses;
final scaffoldKey = GlobalKey<ScaffoldState>();
final formKey = GlobalKey<FormState>();

double _amount;
DateTime _date;
String _category;

void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
if (this.id == 0) expenses.add(Expense(0,
_amount, _date, _category));
else expenses.update(Expense(this.id,
_amount, _date, _category));
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(tailieusharefree.blogspot.com

key: scaffoldKey, appBar: AppBar(
title: Text('Enter expense details'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey, child: Column(
children: [
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.monetization_on),
labelText: 'Amount',
labelStyle:
TextStyle(fontSize: 18)
),
validator: (val) {
Pattern pattern =
r'^[1-9]\d*(\.\d+)?$';
RegExp regex = new
RegExp(pattern);
if
(!regex.hasMatch(val))
return 'Enter a valid
number'; else return null;tailieusharefree.blogspot.com

},
initialValue: id == 0
? '' :
expenses.byId(id).amount.toString(),
onSaved: (val) => _amount
= double.parse(val),
),
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.calendar_today),
hintText: 'Enter date',
labelText: 'Date',
labelStyle:
TextStyle(fontSize: 18),
),
validator: (val) {
Pattern pattern =
r'^((?:19|20)\d\d)[- /.]
(0[1-9]|1[012])[-
/.](0[1-9]|[12][0-9]|3[01])$';
RegExp regex = new
RegExp(pattern);
if
(!regex.hasMatch(val))tailieusharefree.blogspot.com

return 'Enter a
valid date';
else return null;
},
onSaved: (val) => _date =
DateTime.parse(val),
initialValue: id == 0
? '' :
expenses.byId(id).formattedDate,
keyboardType:
TextInputType.datetime,
),
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.category),
labelText: 'Category',
labelStyle:
TextStyle(fontSize: 18)
),
onSaved: (val) =>
_category = val,
initialValue: id == 0 ? ''
:
expenses.byId(id).category.toString(),tailieusharefree.blogspot.com

),
RaisedButton(
onPressed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
}
}
- Ở đây :
TextFormField được sử dụng để tạo một form
thuộc tính validator của TextFormField được sử dụng
để xác thực thành phần biểu mẫu cùng với các mẫu
RegEx.
Hàm _submit được sử dụng cùng với expense để thêm
hoặc cập nhật expenses trong db
- Toàn bộ code trong main.dart như sau :
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';

void main() {
final expenses = ExpenseListModel();
runApp(tailieusharefree.blogspot.com

ScopedModel<ExpenseListModel>(
model: expenses, child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expense',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Expense
calculator'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key:
key);
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(tailieusharefree.blogspot.com

title: Text(this.title),
),
body:
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return ListView.separated(
itemCount: expenses.items ==
null ? 1
: expenses.items.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return ListTile( title:
Text("Total expenses: "
+
expenses.totalExpense.toString(),
style: TextStyle(fontSize:
24,fontWeight:
FontWeight.bold),) );
} else {
index = index - 1; return
Dismissible(
key:
Key(expenses.items[index].id.toString()),
onDismissed:
(direction) {

expenses.delete(expenses.items[index]);tailieusharefree.blogspot.com

Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
"Item with
id, " +

expenses.items[index].id.toString()
+ " is
dismissed"
)
)
);
},
child: ListTile( onTap:
() {
Navigator.push(
context, MaterialPageRoute(
builder:
(context) => FormPage(
id:
expenses.items[index].id, expenses: expenses,
)
));
},
leading:
Icon(Icons.monetization_on), tailieusharefree.blogspot.com

trailing:
Icon(Icons.keyboard_arrow_right),
title:
Text(expenses.items[index].category + ": " +

expenses.items[index].amount.toString() + "
\nspent on " +

expenses.items[index].formattedDate,
style:
TextStyle(fontSize: 18, fontStyle:
FontStyle.italic),))
);
}
},
separatorBuilder: (context,
index) {
return Divider();
},
);
},
),
floatingActionButton:
ScopedModelDescendant<ExpenseListModel>(
builder: (context, child, expenses) {
return FloatingActionButton(
onPressed: () {
Navigator.push(tailieusharefree.blogspot.com

context,
MaterialPageRoute(
builder: (context)
=>
ScopedModelDescendant<ExpenseListModel>(
builder: (context,
child, expenses) {
return FormPage(
id: 0, expenses: expenses, );
}
)
)
);
// expenses.add(
new Expense(
// 2, 1000,
DateTime.parse('2019-04-01 11:00:00'), 'Food'
)
);
//
print(expenses.items.length);
},
tooltip: 'Increment', child:
Icon(Icons.add),
);
}
)
);tailieusharefree.blogspot.com

}
}
class FormPage extends StatefulWidget {
FormPage({Key key, this.id, this.expenses}) :
super(key: key);
final int id;
final ExpenseListModel expenses;

@override
_FormPageState createState() =>
_FormPageState(id: id, expenses: expenses);
}
class _FormPageState extends State<FormPage> {
_FormPageState({Key key, this.id,
this.expenses});
final int id;
final ExpenseListModel expenses;
final scaffoldKey = GlobalKey<ScaffoldState>();
final formKey = GlobalKey<FormState>();
double _amount; DateTime _date;
String _category;
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
if (this.id == 0) expenses.add(Expense(0,
_amount, _date, _category));tailieusharefree.blogspot.com

else expenses.update(Expense(this.id,
_amount, _date, _category));
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey, appBar: AppBar(
title: Text('Enter expense details'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey, child: Column(
children: [
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.monetization_on),
labelText: 'Amount',
labelStyle:
TextStyle(fontSize: 18)
),
validator: (val) {tailieusharefree.blogspot.com

Pattern pattern =
r'^[1-9]\d*(\.\d+)?$';
RegExp regex = new
RegExp(pattern);
if
(!regex.hasMatch(val)) return 'Enter a valid
number';
else return null;
},
initialValue: id == 0 ? ''
:
expenses.byId(id).amount.toString(),
onSaved: (val) => _amount
= double.parse(val),
),
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.calendar_today),
hintText: 'Enter date',
labelText: 'Date',
labelStyle:
TextStyle(fontSize: 18),
),
validator: (val) {tailieusharefree.blogspot.com

Pattern pattern =
r'^((?:19|20)\d\d)[- /.]
(0[1-9]|1[012])[- /.]
(0[1-9]|[12][0-9]|3[01])$';
RegExp regex = new
RegExp(pattern);
if
(!regex.hasMatch(val)) return 'Enter a valid
date';
else return null;
},
onSaved: (val) => _date =
DateTime.parse(val),
initialValue: id == 0 ? ''
: expenses.byId(id).formattedDate,
keyboardType:
TextInputType.datetime,
),
TextFormField(
style: TextStyle(fontSize:
22),
decoration: const
InputDecoration(
icon: const
Icon(Icons.category),
labelText: 'Category',
labelStyle:
TextStyle(fontSize: 18)tailieusharefree.blogspot.com

),
onSaved: (val) =>
_category = val,
initialValue: id == 0 ? ''
: expenses.byId(id).category.toString(),
),
RaisedButton(
onPressed: _submit,
child: new Text('Submit'),
),
],
),
),
),
);
}
}
Bây giờ , ta thử run ứng dụng
Thêm expense mới sử dụng nút "+"
Chỉnh sửa expense bằng cách nhấn vào expense đó
Xoá expense bằng cách lướt expense qua trái
Đây là kết quả tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

tailieusharefree.blogspot.com

tailieusharefree.blogspot.com