+189

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.

image.png

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'),
      );
    });
  }
}

image.png

Full source code: https://dartpad.dev/?id=f6a0be71f183306c02c9389fae37e2d0

Tèn tén ten, bottomsheet đã được show 😄

0_TlOoxdoc4vO42mrI.gif

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.

image.png

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.

image.png

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí