Tự học Flutter 2023

New Flutter cơ bản

Tìm hiểu về BuildContext trong Flutter

Sau một đêm [nhậu] say quắc cần câu với Thịnh Suy, bạn tỉnh dậy và thấy mình nằm trên vỉa hè. Để xác định xem mình đang ở đâu, việc đầu tiên là bạn sẽ xem xét xung quanh: tên đường, các cửa hàng, trạm xe,... những dữ liệu này được gọi là bối cảnh (context), nó giúp bạn có thêm thông tin để dự đoán được mình đang ở chỗ nào.

Trong Flutter, cũng có những trường hợp mà widget sẽ cần biết nó đang ở đâu, widget cha của nó là ai,.. Để có được những thông tin đó, nó cần một một cái BuildContext để tìm ra manh mối. Nghe cũng khá giống cái context chúng ta vừa nói nhỉ. Vậy, BuildContext trong Flutter nó là cái gì?

BuildContext là gì?

Đầu tiên chúng ta sẽ quay trở lại một chút với kiến thức cơ bản về widget. Chúng ta biết rằng Flutter tạo ra giao diện người dùng bằng cách sử dụng kết hợp các widget. Những widget này có thể lồng nhau, tạo ra một widget tree (cây widget).

Cần nhắc lại rằng Widget là bản thiết kế cho một "mảnh giao diện" (một nút bấm cũng có thể xem là một mảnh, một đoạn text là một mảnh, một màn hình cũng là một mảnh), nó chỉ là bản thiết kế nên nó sẽ không có các thông tin như vị trí ở trên cây, parent, children,... Nó chỉ có các thông tin đơn thuần về việc mảnh giao diện nên hiển thị như thế nào (màu sắc, cỡ chữ,...).

Nhưng như chúng ta đã nói ở trên, có những trường hợp mà chúng ta cần thông tin về widget cha thì làm thế nào?

May mắn là Flutter không chỉ có mỗi widget tree, mà nó còn tạo ra một cái khác gọi là element tree. Với mỗi widget Flutter sẽ tạo ra một element tương ứng (StatelessWidget sẽ có StatelessElement được tạo ra và StatefulWidget sẽ có StatefulElement được tạo), do đó chúng ta sẽ có thêm 1 element tree.

Mỗi element object này (StatelessElement hoặc StatefulElement) có thể xem là đại diện cho một Widget tại một vị trí cụ thể trên cây. Ngoài việc giữ tham chiếu tới widget nó còn lưu trữ thêm các thông tin khác như là parents, children...

Việc vì sao Flutter lại tạo ra cái element tree này, chúng ta sẽ tìm hiểu trong một bài viết khác. Hiện tại bạn chỉ cần biết rằng có một cái element tree tồn tại song song với widget tree là được.

Vậy bây giờ nếu như chúng ta ở một widget bất kỳ và có được element object tương ứng, dựa vào element object đúng ta hoàn toàn có thể truy ngược lên và tìm ra parent widget chúng ta muốn.

BuildContext chính là cái Element object này.

  • Mỗi widget đều có một BuildContext.
  • Nó có thông tin về parent widget
  • Vậy sao không gọi là Element luôn đi mà gọi là BuildContext? Uhm, BuildContext là một interface nhằm hạn chế chúng ta truy cập vào các phương thức của elements. Hơn nữa cá nhân mình thấy cái tên BuildContext cũng thể hiện rõ ràng ý nghĩa hơn. Ngoài ra element còn là cái gì đó mà Flutter muốn ẩn đi ở bên dưới widget.

    Bây giờ nếu như muốn đưa nó lên mức khái niệm, chúng ta có thể nói rằng BuildContext bản chất chính là Element object, hoặc nói cách khác BuildContext là thứ có thể giúp chúng ta xác định vị trí của widget trên widget tree.

    Làm sao để truy cập BuildContext

    Chúng ta có thể truy cập BuildContext ở hai nơi.

    Đầu tiên, tất nhiên là ở trong hàm build của widget (cả Stateless và Stateful widget).

    Tiếp theo là trong các hàm của state của StatefulWidget (ví dụ như ở trong initState).

    Ví dụ thực tế

    Trên thực tế có rất nhiều trường hợp mà chúng ta cần đến BuildContext, có thể kể đến:

  • Theme: Theme.of(context)
  • Điều hướng: Navigator.of(context)
  • Truy vấn kích thước: MediaQuery.of(context)
  • Build listview item: itemBuilder: (context, index) {}
  • ...
  • Bên dưới là ví dụ khi người dùng bấm vào một nút bấm nào đó, bạn sẽ hiển thị một thông báo (snackbar message):

    
    ElevatedButton(
      onPressed: () {
        const snackBar = SnackBar(content: Text("This is a message"));
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      },
      child: const Text("Click to show snack bar message"),
    )
                          

    showSnackBar là một hàm của ScaffoldMessengerState. Mà để access vô cái state này thì cần tìm tới cái ScaffoldMessenger widget trước.

    Bản chất của ScaffoldMessenger.of(context) đơn giản là nó dựa vào cái context được đưa vào để tìm kiếm cái ScaffoldMessenger widget gần nhất nằm ở phía trên. Đi vào hàm này nó sẽ như thế này:

    context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>()