- vừa được xem lúc

Chuyên án điều tra: Giải mã bí ẩn Key trong Flutter

0 0 1

Người đăng: Henry Techie

Theo Viblo Asia

Đã quá nhàm chán với những bài viết học thuật? Vậy thì hôm nay mình sẽ mang đến một bài viết với giọng văn hình sự, mời anh em giải trí.

Mở đầu chuyên án

Key xuất hiện tràn ngập trong Flutter, từ StatefulWidget tới StatelessWidget. Vậy nhưng tưởng như rất gần mà ngờ đâu đã quá xa, nghĩ rằng đã trở nên thân thuộc mà lại vô cùng bí ẩn. Tuy anh em Flutter developer thường xuyên làm việc với Widget, nhưng Key - thứ luôn âm thầm đứng phía sau các vụ chuyển giao quyền lực - lại hiếm khi được chú ý.

Hôm nay, đội cảnh sát hình sự Flutter Việt Nam sẽ đưa anh em đi sâu vào ngõ ngách của băng đảng Flutter, giải mã vai trò của Key trong việc tối ưu hóa hiệu năng ứng dụng Flutter, đồng thời khám phá các best practice để sử dụng chúng.

Đi tìm ông trùm đứng sau

Định nghĩa của Key trong document nói rằng:

A Key is an identifier for Widgets, Elements and SemanticsNodes.

A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.

Tạm dịch:

Key là mã định danh cho Widget, Element và SemanticsNodes.

Một widget mới chỉ được sử dụng để cập nhật một element đã tồn tại nếu key của nó giống với key của widget hiện tại được liên kết với element đó.

Như anh em đã biết thì trong Flutter, mọi thứ đều là Widget. Băng đảng Widget này gồm nhiều thành phần đã quen mặt với anh em như Row, Column, Container... Thế nhưng lũ Widget này chỉ là tay chân lâu la, theo các thông tin tình báo chúng tôi có được, băng đảng này được điều hành bởi một ông trùm khét tiếng: Element.

Hắn là kẻ thao túng tất cả Widget, từ việc gọi hàm initState, build, dispose của Widget đến vai trò quản lý Widget Tree. Element đồng thời là một mắt xích quan trọng kết nối giữa WidgetRenderObject - kẻ giúp vẽ UI lên màn hình – để điều chế ma... à nhầm, để tạo nên những tác phẩm nghệ thuật tinh xảo.

Tuy nhiên, hôm nay chúng ta không bắt ôm trùm hay triệt phá toàn bộ băng đảng này, chỉ cần đơn giản nhớ rằng Element là kẻ đứng sau điều phối tất cả. Cá nuôi chưa lớn thì chưa nên cất vó. Mục tiêu của chuyên án là điều tra Key và 4 gã tứ đại cao thủ.

Key

Khi bạn rebuild ứng dụng Flutter, bạn có biết chuyện gì xảy ra trong bóng tối không? Element quyết định những Widget nào sẽ được giữ lại, thay thế, hay hủy bỏ. Và đây là nơi mà Key bắt đầu thể hiện quyền lực.

Trong giới Widget, mỗi khi thay đổi diễn ra, Widget không chỉ đơn giản là update mà là bị huỷ bỏ rồi tái sinh. Key chính là căn cước công dân để các Widget giữ nguyên danh tính của mình khi tái sinh. Sau khi Widget tree bị rebuild, Element sẽ dựa vào Widget typeKey để quyết định xem Element có bị rebuild hay không. Trước mắt, nếu Widget type khác nhau, chắc chắn Element sẽ bị huỷ bỏ đi tạo lại. Mà bạn biết đấy, việc rebuild cả ông trùm thì sẽ rất tốn kém so với việc rebuild đám lâu la Widget. Việc này gây ra những vấn đề không mong muốn về performance và đôi khi làm ứng dụng của bạn lag.

Còn nếu Widget type giống nhau, chúng sẽ tiếp tục so sánh đến Key, nếu Key giống nhau thì Element chỉ update widget. Ngược lại, Element sẽ bị deactivate, nghĩa là tạm gỡ ra khỏi Element tree và có khả năng được gắn lại vào tree sau.

Key gồm 2 loại chính là LocalKeyGlobalKey, trong đó LocalKey lại được chia ra thành UniqueKey, ValueKeyObjectKey. Sau đây, chúng ta sẽ đi bóc trần từng gã.

UniqueKey - Sát thủ không thể truy dấu

Một kẻ bí ẩn, thoắt ẩn thoắt hiện, không bao giờ xuất hiện hai lần với cùng một giá trị. Hắn tạo ra các giá trị duy nhất để giúp Flutter phân biệt hai Widget dù chúng có cùng type. Chúng ta cần tới hắn khi không muốn tái sử dụng bất kỳ Widget nào, đảm bảo rằng Widget được rebuild hoàn toàn.

Ví dụ với đoạn code ban đầu như sau:

final names = ['Henry', 'Techie', 'Nam', 'Anh', 'Nguyen']; class HomePage extends StatefulWidget { const HomePage({super.key});  State<HomePage> createState() => _HomePageState();
} class _HomePageState extends State<HomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); }  Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Demo Key')), body: ListView.builder( itemCount: _counter, itemBuilder: (context, index) => Item( text: names[index], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: Icon(Icons.add), ), ); }
} class Item extends StatefulWidget { final String text; const Item({super.key, required this.text});  State<Item> createState() => _ItemState();
} class _ItemState extends State<Item> {  void initState() { super.initState(); debugPrint('[_ItemState.initState] key = ${widget.key}, text = ${widget.text}'); }  Widget build(BuildContext context) { debugPrint('[_ItemState.build] key = ${widget.key}, text = ${widget.text}'); return Text(widget.text); }
}

Chúng ta có thể xem log và thấy chỉ widget mới nhất được tạo là gọi đến initState, còn những widget khác chỉ rebuild:

[_ItemState.build] key = null, text = Henry
[_ItemState.build] key = null, text = Techie
[_ItemState.build] key = null, text = Nam
[_ItemState.build] key = null, text = Anh
[_ItemState.initState] key = null, text = Nguyen
[_ItemState.build] key = null, text = Nguyen

Nhưng khi thêm UniqueKey vào Item như sau:

body: ListView.builder( itemCount: _counter, itemBuilder: (context, index) => Item( key: UniqueKey(), text: names[index], ),
),

Mọi chuyện đã trở nên hoàn toàn khác, tất cả các widget đều bị tạo lại:

[_ItemState.initState] key = [#c24b5], text = Henry
[_ItemState.build] key = [#c24b5], text = Henry
[_ItemState.initState] key = [#02979], text = Techie
[_ItemState.build] key = [#02979], text = Techie
[_ItemState.initState] key = [#0a0db], text = Nam
[_ItemState.build] key = [#0a0db], text = Nam
[_ItemState.initState] key = [#23d4d], text = Anh
[_ItemState.build] key = [#23d4d], text = Anh
[_ItemState.initState] key = [#614a4], text = Nguyen
[_ItemState.build] key = [#614a4], text = Nguyen

ValueKey - Gã đồ tể đáng tin cậy

Hắn là một kẻ đầu óc đơn giản nhưng làm việc rất hiệu quả, ValueKey là lựa chọn hoàn hảo khi bạn có một giá trị định danh rõ ràng cụ thể, chẳng hạn như String hoặc int. Gã đồ tể này giúp Element biết đâu là Widget cần giữ lại chỉ dựa vào giá trị định danh đó, vậy nên chúng ta có thể tái sử dụng Widget khi giá trị Key không thay đổi.

Đầu tiên, chúng ta hãy thử update ListView trong ví dụ trên thành ReorderableListView:

body: ReorderableListView( onReorder: (oldIndex, newIndex) { setState(() { if (newIndex > oldIndex) newIndex -= 1; final item = names.removeAt(oldIndex); names.insert(newIndex, item); }); }, children: names.map((item) { return Item( key: ValueKey(item), text: item, ); }).toList(),
),

Bây giờ mỗi khi kéo thả để thay đổi thứ tự các item, chúng ta sẽ thấy các item được rebuild:

I/MIUIInput(21307): [MotionEvent] ... { action=ACTION_DOWN... } moveCount:0
[_ItemState.build] key = [<'Henry'>], text = Henry
I/MIUIInput(21307): [MotionEvent] ... { action=ACTION_UP... } moveCount:52
[_ItemState.build] key = [<'Henry'>], text = Henry
[_ItemState.build] key = [<'Techie'>], text = Techie
[_ItemState.build] key = [<'Nam'>], text = Nam
[_ItemState.build] key = [<'Anh'>], text = Anh
[_ItemState.build] key = [<'Nguyen'>], text = Nguyen

Vậy, nếu chúng ta thử thay đổi giá trị của một item sau khi kéo thả bằng cách update function onReorder như sau thì sao?

onReorder: (oldIndex, newIndex) { setState(() { if (newIndex > oldIndex) newIndex -= 1; final item = '${names.removeAt(oldIndex)} Changed'; names.insert(newIndex, item); });
},

Khi đó, item đã thay đổi key sẽ bị tạo lại như sau:

[_ItemState.initState] key = [<'Henry Changed'>], text = Henry Changed
[_ItemState.build] key = [<'Henry Changed'>], text = Henry Changed

ObjectKey - Quân sư đa mưu túc trí

Ngược lại với Value Key, gã này là một bậc thầy chiến lược, giữ trong tay cả một object phức tạp. ObjectKey dựa trên tham chiếu đến object. 2 Key chỉ được coi là giống nhau nếu chúng tham chiếu đến cùng một object.

Trong ví dụ này, ObjectKey đảm bảo rằng Widget được giữ nguyên khi object không thay đổi.

class ObjectKeyExample extends StatelessWidget { final List<Person> people = [ Person(name: "Alice"), Person(name: "Bob"), ];  Widget build(BuildContext context) { return ListView( children: people.map((person) { return ListTile( key: ObjectKey(person), title: Text(person.name), ); }).toList(), ); }
} class Person { final String name; Person({required this.name});
}

GlobalKey - Người quản gia quyền năng

Trong băng đảng, GlobalKey là kẻ mạnh nhất. Hắn biết tất cả mọi thứ trong Widget Tree. Không chỉ lưu danh tính, hắn còn quản lý toàn bộ trạng thái và cho phép truy cập trực tiếp đến State. Điều này mang lại sự linh hoạt nhưng cũng dễ bị lạm dụng.

Ví dụ thường thấy nhất là dùng GlobalKey để kiểm soát và xác nhận trạng thái của Form.

class GlobalKeyExample extends StatelessWidget { final GlobalKey<FormState> formKey = GlobalKey<FormState>();  Widget build(BuildContext context) { return Form( key: formKey, child: Column( children: [ TextFormField(validator: (value) => value!.isEmpty ? 'Required' : null), ElevatedButton( onPressed: () { if (formKey.currentState!.validate()) { print('Form is valid!'); } }, child: Text('Submit'), ), ], ), ); }
}

Best Practices khi dùng Key

Key không chỉ là một công cụ, mà là bảo bối để kiểm soát ứng dụng. Việc hiểu và sử dụng đúng Key không chỉ giúp bạn tối ưu hóa performance mà còn đảm bảo logic của ứng dụng luôn ổn định và chính xác. Lạm dụng nó có thể làm code trở nên phức tạp không cần thiết, vậy nên hãy chỉ sử dụng khi cần. Hãy nhớ rằng trong thế giới Flutter đầy biến động này, Key chính là chiếc chìa khóa cho sự mượt mà của ứng dụng mà anh em đang phát triển!

🔔 Blog: henrytechie.com

☕️ Facebook: Henry Techie

☁️ TikTok: @henrytechie

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 299

- vừa được xem lúc

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.

1 1 355

- vừa được xem lúc

Dart Cheat Sheet - Full bộ "bỏ túi" các syntax trong ngôn ngữ Dart

Dart là một ngôn ngữ mới dùng cho cả Mobile và Web với Flutter Framework, thậm chí dùng cho Backend. Để giúp mọi người dễ dàng nắm bắt ngôn ngữ này hơn, 200lab Education đã tổng hợp thành bộ "bí tịch" dưới đây để tra cứu nhanh, tăng tốc phát triển phần mềm.

0 0 54

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 2: StatefulWidget vs StatelessWidget. Khi nào thì cần sử dụng cái nào?

Lời mở đầu. Ở bài trước, chúng ta đã dừng lại ở một kết thúc mở.

0 0 102

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 4: Lột trần InheritedWidget

Lời mở đầu. Trong đoạn kết của phần 2, chúng ta đã đối mặt với 1 bài toán: Làm thế nào để truyền data từ một widget cha nào đó xuống thẳng widget chắt mà không phải sử dụng constructor để truyền xuống

0 0 71

- vừa được xem lúc

Chinh phục RxDart Flutter trong 3 nốt nhạc. Nốt thứ nhất: Stream và giải thích các thuật ngữ

Lời mở đầu. Mình viết series này với mục đích chia sẻ kiến thức về RxDart trong Flutter.

0 0 80