Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?
Lời mở đầu
Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter. Vậy mới thú vị và xứng đáng để chúng ta bỏ công tìm hiểu và chinh phục. Trong số những bí ẩn đó, phải kể đến là StatelessWidget
, Key
, BuildContext
.
// trích lại 1 phần code trong phần 1
class ColumnWidget extends StatelessWidget {
const ColumnWidget({Key key,}) : super(key: key); // Key là gì?
Widget build(BuildContext context) { // BuildContext là gì?
...........................
StatelessWidget thì đã được làm sáng tỏ trong phần 2: StatefulWidget vs StatelessWidget. Khi nào thì cần sử dụng cái nào?. Bây giờ, chúng ta sẽ tiếp tục tìm hiểu về BuildContext
.
1. Tình huống
Chúng ta làm cái app có 1 màn hình, có 1 cái FloatingActionButton. Click vào button đó sẽ show một bottomsheet với nội dung: "Flutter From Zero to Hero". Okay, app khá đơn giản. Đoạn code sau dùng để show 1 bottomsheet:
Scaffold.of(context).showBottomSheet(
(context) => Text('Flutter From Zero to Hero'),
);
Full source code: https://dartpad.dev/?id=71d9edbd335726c3bcc8b87abf8acd6d
Khi click vào button kia thì bottomsheet ko thấy show mà chỉ cái cái lỗi đỏ lè trên console với nội dung:
Scaffold.of() called with a context that does not contain a Scaffold.
No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of()
Tạm dịch: hàm Scaffold.of()
được gọi với một cái context
, mà cái context
này nó không chứa widget Scaffold
nào cả.
Để điều tra nguyên nhân gây ra lỗi. Tất nhiên việc đầu tiên là phải click vào bên trong hàm Scaffold.of(context)
xem. Bên trong nó có nói công dụng của hàm này là: "Mi truyền vào cho ta một biến context
, ta sẽ giúp mi tìm trong những widget cha của mi, người cha mà có type là Scaffold và gần mi nhất".
Tóm lại là hàm Scaffold.of(context)
là một hàm tìm kiếm Scaffold dựa vào context được truyền vào. Vậy context là gì
2. BuildContext là gì
BuildContext
được Flutter trao cho đôi mắt thiên lý nhãn. Với đôi mắt thần thánh này, nó sẽ biết widget này được đặt ở vị trí nào trên widget tree. Hay nói cách khác, một BuildContext như là một tham chiếu (reference) đến cái vị trí của widget (widget's location) trong widget tree. Như chúng ta đã biết ở bài trước, mỗi loại widget đều có hàm build()
, mỗi hàm build
đều nhận 1 BuildContext
làm argument. Như vậy mỗi Widget đều có 1 BuildContext
đại diện cho vị trí của chính Widget đó trên widget tree.
À nói đến đây thì ta đủ hiểu rồi. Nguyên nhân là do ta truyền sai context (tức là ta truyền sai vị trí để hàm Scaffold.of()
bắt đầu tìm kiếm). Do ta truyền vào context
của MyHomePage
, nên hàm Scaffold.of
sẽ đi tìm từ vị trí MyHomePage
tìm lên trên các widget cha để xem có widget nào là Scaffold
không. Tất nhiên là không có thằng nào rồi, vì MyHomePage
chỉ có 1 widget cha duy nhất là MaterialApp
- ko phải Scaffold
.
3. Fix bug
Tất nhiên, bug do truyền sai context
thì cách fix sẽ là truyền đúng context
vào hàm Scaffold.of
rồi
Rất đơn giản, chỉ cần extract thằng FloatingActionButton ra class riêng đặt tên là MyButtonWidget
. Thay vì sử dụng context của MyHomePage
, ta sẽ sử dụng context của MyButtonWidget
để truyền vào hàm Scaffold.of
vì từ context
đó, Scaffold.of
sẽ tìm thấy được widget cha Scaffold
gần nhất.
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: MyButtonWidget(),
);
}
}
class MyButtonWidget extends StatelessWidget {
Widget build(BuildContext context) {
return FloatingActionButton(onPressed: () {
Scaffold.of(context).showBottomSheet(
(context) => Text('Flutter From Zero to Hero'),
);
});
}
}
Full source code: https://dartpad.dev/?id=f6a0be71f183306c02c9389fae37e2d0
Tèn tén ten, bottomsheet đã được show
Thế giờ tui không thích extract widget ra class riêng thì fix được không. Sao lại không, thoải mái luôn. Khi đó có một widget gọi là Builder
sẽ support chúng ta. Cụ thể chúng ta sẽ sử dụng widget Builder để wrap widget FloatingActionButton lại.
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(onPressed: () {
Scaffold.of(context).showBottomSheet(
(context) => Text('Flutter From Zero to Hero'),
);
});
}),
);
}
}
Full source code: https://dartpad.dev/?id=a40261cef20862154902eb441c21c980
Khi đó, chúng ta Widget Tree sẽ như thế này và chúng ta sử dụng context của widget Builder
để truyền vào hàm Scaffold.of
nên nó sẽ hoạt động đúng như mong đợi.
Như vậy, chúng ta có đến 2 cách để fix bug trên. Thật ra có 1 cách thứ 3 nữa là sử dụng GlobalKey
. Chúng ta sẽ đi sâu vào GlobalKey ở một bài khác.
4. Nếu có đến 2 Scaffold thì sao?
Giả sử chúng ta có đến 2 Scaffold đều là cha của Builder
thì sao?. Thử xem nhé!. Ta sẽ tạo ra 2 Scaffold, một cái có backgroundColor
màu green, một cái màu pink. Khi click vào button ta sẽ print ra xem hàm Scaffold.of
sẽ trả về Scaffold dựa vào giá trị backgroundColor
của nó.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF1B5E20), // green
body: Scaffold(
backgroundColor: Color(0xffe91e63), // pink
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(onPressed: () {
print(Scaffold.of(context).widget.backgroundColor);
});
}),
),
);
}
}
Full source code: https://dartpad.dev/?id=7c217d6eaf8675eb12048714010900d6
Console log:
Color(0xffe91e63) // màu hồng
Như vậy, Scaffold màu hồng tức là Scaffold gần với Builder nhất đã được trả về.
Widget Tree sẽ trông như thế này.
Lời kết
Sau này chúng ta sẽ gặp rất nhiều hàm of
truyền vào context như Theme.of, MediaQuery.of, Navigator.of, Provider.of,... và thêm 1 loại Widget nữa có tên là InheritedWidget
cũng sử dụng hàm of
này. Nếu như bạn tò mò muốn biết InheritedWidget
là gì thì hãy cùng tôi khám phá nó trong phần tiếp theo nhé.
Đây mới chỉ là lần lột trần nàng Flutter đầu tiên, những lần sau sẽ cố gắng lột hết rồi mới dám lâm trận các bạn ạ =)). Thật sự viết lý thuyết và code demo từng chút nhỏ vậy cũng không phê lắm, muốn lâm trận bằng dự án thiết thực luôn cơ. Mình cũng đấu tranh dữ lắm giữa lột tiếp hay lâm trận luôn, nhưng có câu: "Ta vung kiếm 1 bài nhưng mài kiếm mười mấy bài - Tư Mã Ý". Cứ mài kiếm cho bén vào, đảm bảo nàng sẽ đổ ngay trong lần vung kiếm đầu tiên =)). Kiên trì ắt sẽ có thiên hạ
Lại cứ phải note nhẹ: Click follow để nhận thông báo khi có bài viết mới nhé =))
Đọc tiếp phần 4: Phần 4: Lột trần InheritedWidget
All rights reserved