Keys trong Flutter
1. Key là gì?
Key là một định danh duy nhất giúp Flutter phân biệt các widgets trong Widget Tree. Khi không có key, Flutter dựa vào vị trí và kiểu của widget để xác định widget nào là widget nào khi rebuild.
Không có Key: Có Key:
┌──────────────┐ ┌──────────────┐
│ Widget A [0] │ │ Widget A [key1] │
├──────────────┤ ├──────────────┤
│ Widget B [1] │ │ Widget B [key2] │
├──────────────┤ ├──────────────┤
│ Widget C [2] │ │ Widget C [key3] │
└──────────────┘ └──────────────┘
↓ ↓
Flutter dựa vào Flutter dựa vào
INDEX để nhận diện KEY để nhận diện2. Tại sao cần Key?
Vấn đề khi không có Key
Khi thứ tự hoặc số lượng widgets thay đổi, Flutter có thể nhầm lẫn widget nào là widget nào:
Trước khi xóa Item B: Sau khi xóa Item B:
┌──────────────┐ ┌──────────────┐
│ Item A [0] │ │ Item A [0] │ ← Vẫn đúng
├──────────────┤ ├──────────────┤
│ Item B [1] │ → │ Item C [1] │ ← Flutter nghĩ đây là Item B!
├──────────────┤ └──────────────┘
│ Item C [2] │
└──────────────┘Hậu quả: State của Item B có thể gán nhầm cho Item C!
Giải pháp với Key
Trước khi xóa Item B: Sau khi xóa Item B:
┌──────────────────┐ ┌──────────────────┐
│ Item A [keyA] │ │ Item A [keyA] │ ← Match by key
├──────────────────┤ ├──────────────────┤
│ Item B [keyB] │ → │ Item C [keyC] │ ← Match by key, đúng state!
├──────────────────┤ └──────────────────┘
│ Item C [keyC] │
└──────────────────┘3. Các loại Key
3.1 ValueKey
Dùng khi bạn có một giá trị duy nhất để định danh:
ValueKey('user-123')
ValueKey(item.id)
ValueKey(email)┌─────────────────────────────────────┐
│ ValueKey<T> │
├─────────────────────────────────────┤
│ So sánh bằng value.operator==() │
│ │
│ ValueKey(1) == ValueKey(1) ✓ │
│ ValueKey('a') == ValueKey('a') ✓ │
│ ValueKey(1) == ValueKey(2) ✗ │
└─────────────────────────────────────┘3.2 ObjectKey
Dùng khi bạn muốn so sánh bằng identity (cùng object instance):
ObjectKey(userObject)
ObjectKey(myInstance)┌─────────────────────────────────────┐
│ ObjectKey │
├─────────────────────────────────────┤
│ So sánh bằng identical() │
│ │
│ obj1 = User('A') │
│ obj2 = User('A') │
│ ObjectKey(obj1) == ObjectKey(obj1) ✓│
│ ObjectKey(obj1) == ObjectKey(obj2) ✗│
└─────────────────────────────────────┘3.3 UniqueKey
Tạo key mới mỗi lần (dùng khi muốn force rebuild):
UniqueKey() // Mỗi lần gọi tạo key khác nhau┌─────────────────────────────────────┐
│ UniqueKey │
├─────────────────────────────────────┤
│ Luôn tạo key mới │
│ │
│ UniqueKey() != UniqueKey() ✓ │
│ Sử dụng: Force widget rebuild │
└─────────────────────────────────────┘3.4 GlobalKey
Key có thể truy cập từ bất kỳ đâu trong app:
final formKey = GlobalKey<FormState>();
// Sau đó: formKey.currentState?.validate()┌─────────────────────────────────────────────┐
│ GlobalKey<T> │
├─────────────────────────────────────────────┤
│ • Định danh duy nhất trong toàn bộ app │
│ • Có thể truy cập State và Widget │
│ • Tốn performance - dùng hạn chế! │
│ │
│ formKey.currentState → Access State │
│ formKey.currentWidget → Access Widget │
│ formKey.currentContext → Access Context │
└─────────────────────────────────────────────┘4. Quyết định Key Selection Tree
Cần Key không?
│
┌────────────┴────────────┐
│ │
Danh sách Widget cố định
có thể thay đổi không đổi thứ tự
│ │
CẦN KEY KHÔNG CẦN KEY
│
┌───────┴───────┐
│ │
Có ID Không có ID
unique? unique?
│ │
ValueKey(id) ObjectKey(object)
hoặc
UniqueKey() (rare)5. Ví dụ minh họa: Checkbox List Bug
Không có Key (Bug!)
Initial State: Sau khi xóa "Apple":
┌─────────────────────┐ ┌─────────────────────┐
│ [✓] Apple [0] │ │ [✓] Banana [0] │ ← Checkbox state của Apple!
├─────────────────────┤ ├─────────────────────┤
│ [ ] Banana [1] │ → │ [ ] Cherry [1] │ ← Checkbox state của Banana!
├─────────────────────┤ └─────────────────────┘
│ [ ] Cherry [2] │
└─────────────────────┘
Flutter giữ state theo INDEX, không theo item!Có Key (Đúng!)
Initial State: Sau khi xóa "Apple":
┌───────────────────────────┐ ┌───────────────────────┐
│ [✓] Apple [key: apple] │ │ [ ] Banana [key: banana]│ ← Đúng state!
├───────────────────────────┤ ├───────────────────────┤
│ [ ] Banana [key: banana]│ → │ [ ] Cherry [key: cherry]│ ← Đúng state!
├───────────────────────────┤ └───────────────────────┘
│ [ ] Cherry [key: cherry]│
└───────────────────────────┘
Flutter giữ state theo KEY!6. Khi nào dùng Key?
✅ Nên dùng Key
| Trường hợp | Loại Key |
|---|---|
| ListView.builder với items có thể reorder/delete | ValueKey(item.id) |
| AnimatedList | ValueKey hoặc ObjectKey |
| Form validation | GlobalKey<FormState> |
| Access widget state từ bên ngoài | GlobalKey |
| Shuffle widgets với animation | ValueKey |
❌ Không cần Key
| Trường hợp |
|---|
| Danh sách tĩnh không thay đổi |
| Widgets không có state |
| Thứ tự widgets không bao giờ thay đổi |
7. Performance Considerations
┌──────────────────────────────────────────────────────┐
│ Performance Impact │
├──────────────────────────────────────────────────────┤
│ │
│ LocalKey (ValueKey, ObjectKey, UniqueKey) │
│ └── Tốt ✓ │
│ • So sánh nhanh │
│ • Chỉ trong phạm vi parent │
│ │
│ GlobalKey │
│ └── Tốn performance ⚠️ │
│ • Cần maintain registry toàn app │
│ • Chỉ dùng khi thực sự cần │
│ │
└──────────────────────────────────────────────────────┘📝 Tóm tắt
| Key Type | So sánh bằng | Use case |
|---|---|---|
ValueKey | == (value equality) | ID, string, number |
ObjectKey | identical (same instance) | Object reference |
UniqueKey | Luôn khác nhau | Force rebuild |
GlobalKey | Registry lookup | Access state/widget |
Quy tắc vàng:
Dùng Key khi thứ tự hoặc số lượng widgets có thể thay đổi và widgets có state riêng.
Last updated on