Trước đây khi lập trình và duy trì ứng dụng Passio App, việc hiểu sai ý nghĩa của context trong toàn bộ ứng dụng có thể gây nên các lỗi (bugs) tiềm ẩn như context bị nullable, context không truy cập được đến đúng class xử lý trong widget tree… Nên trong bài viết này, tôi sẽ giúp các bạn hiểu đúng về context trong ứng dụng Flutter nhé.
Định nghĩa
Trong Flutter, context là một đối tượng cung cấp về vị trí hiện tại của widget trong widget tree. Nó cung cấp các thông tin về môi trường, các dịch vụ mà widget có thể truy cập.
Tại sao context lại quan trọng?
Context đặc biệt quan trọng vì các lý do sau:
- Truy cập tài nguyên: Context cho phép truy cập các tài nguyên được xác định trong widget tree chính xác hơn từ trong ra ngoài. Ví dụ, từ widget có thể truy cập các dữ liệu liên quan đến theme, ngôn ngữ, … và nó thực hiện được điều đó bằng các context khác được liên kết với nó, hoặc đơn giản là tham chiếu từ widget hiện tại ra ngoài cho đến khi tìm được context tương ứng.
- Quản lý trạng thái (state management): cũng vì cơ chế như trên thì context cũng có thể được sử dụng để quản lý trạng thái kế thừa từ các widget cấp cao hơn trong widget tree. Lợi ích này giúp cho không cần phải truyền prop (thuộc tính) lần lượt qua các widget.
- Thực hiện điều hướng: Navigator là 1 feature rất mạnh của Flutter. Các phương thức để điều hướng như Navigator.of(context) yêu cầu quyền truy cập vào context hiện tại của Routing để điều hướng chính xác. Phần này lát nữa tôi sẽ chỉ ra một số lỗi mà khi dev rất dễ mắc phải.
Hiểu về Widget Tree
Nếu đã sử dụng các framework có sử dụng virtual dom thì các bạn sẽ không lạ gì khái niệm này, chỉ là tên gọi có khác đi như là: Element Tree, Component Tree và trong Flutter nó được gọi là Widget Tree. Hiểu đơn giản thì nó đại diện cho một Hệ thống phân cấp các widget (the hierarchy of widgets) để tạo nên giao diện cho người dùng. Widget đóng vai trò là bộ rễ (root) trong ứng dụng là một trong hai widget là MaterialApp hoặc Cupertino App. Widget này đóng vai trò là điểm bắt đầu để từ đó các widget khác được phát triển có thể lồng vào, tạo thành các bố cục giao diện người dùng phức tạp.
Các phương thức được context cung cấp
context.findAncestorStateOfType();
context.findAncestorWidgetOfExactType();
context.findRenderObject();
context.findRootAncestorStateOfType();
context.findAncestorRenderObjectOfType();
context.getElementForInheritedWidgetOfExactType();
context.getInheritedWidgetOfExactType();
Ngoài các phương thức mặc định trên thì chúng ta có thể bổ sung được vào context thông qua việc khai báo extension, ví dụ như:
extension LocalizationContext on BuildContext { String translate( String text, { Map<String, String> params = const {}, }) => AppLocalizations.of(this).translate(text, params: params); String trans( String text, { Map<String, String> params = const {}, }) => translate(text, params: params); AppLocalizations get l10n => AppLocalizations.of(this); }
Một số cách sử dụng context sai
1. Không đóng được dialog, bottom sheet, router do Navigator.of(context) bị nullable.
Lỗi này được xác định là context bị truyền sai nên khi gọi, context được sử dụng không thể truy cập được widget mong muốn.
showModalBottomSheet( context: context, builder: (_) => InkWell( onTap: Navigator.of(context).pop, child: const Text('Close'), ), );
Lỗi này sửa thì rất đơn giản, chỉ cần sử dụng đúng context được pass ra từ hàm builder thì lúc này context được gọi sẽ được bind Navigator của bottom sheet.
showModalBottomSheet(
context: context,
builder: (context) => InkWell(
onTap: Navigator.of(context).pop,
child: const Text('Close'),
),
);
2. Sử dụng context sau khi xử lý bất đồng bộ
Vẫn là ví dụ trên nhưng lần này, context được gọi ngay sau khi xử lý bất động bộ
showModalBottomSheet( context: context, builder: (context) => InkWell( onTap: () async { await Future.delayed(const Duration(seconds: 2)); Navigator.of(context).pop(); }, child: const Text('Close'), ), );
Lỗi này thì ở các version Flutter thấp (3.0.xx) các dev thường không để ý và dễ dàng bỏ qua. Nhưng lỗi này cũng có thể dẫn đến việc không đóng được màn hoặc là tệ hơn nữa là app sẽ bị crash. Cách giải quyết vấn đề này thì cũng rất đơn giản, chỉ cần check context vẫn đang mounted thông qua hàm context.mounted == true trước khi gọi là được.
await Future.delayed(const Duration(seconds: 2));
if (!context.mounted) return;
Navigator.of(context).pop();
3. Hiểu sai về khái niệm context.read<T>() trong thư viện bloc của flutter.
Thật ra lỗi này gặp phải cũng là nguyên nhân của 2 lỗi tôi kể trên. Vì đặt bloc sai vị trí trong widget trên dẫn đến khi truy cập bị nullable. Resolve được bug này thì các bạn nên đọc lại bài viết để rõ hơn nhé.
Tổng kết
Hy vọng bài viết này sẽ cung cấp cho các bạn một số kiến thức nhất định về context trong Flutter. Hãy cùng nhau chia sẻ về các kiến thức bổ ích trong lập trình các bạn nhé. Thân chào và hẹn gặp lại.