Danh sách có hai nhóm cùng tên “Mobile”, và DropdownButton báo rằng giá trị đang chọn không khớp đúng một item. Thường lỗi bắt đầu khi ứng dụng dùng nhãn hiển thị làm định danh. Hai dòng giống chữ không nhất thiết là cùng một thực thể; dropdown cần một giá trị phân biệt chúng rõ ràng.
Bài đối chiếu Flutter API ngày 09/11/2026. Ví dụ Material dưới đây được rà theo tài liệu và chữ ký widget, chưa chạy Flutter SDK, build, widget test hoặc thiết bị. Khi áp dụng, kiểm tra lại trên phiên bản SDK và nền tảng của dự án.
Bài tập trung vào màn hình đang dùng DropdownButton. Trang lớp DropdownButton còn hướng dẫn chuyển sang DropdownMenu cho ứng dụng Material 3; hai API có cách quản lý lựa chọn khác nhau, nên không áp dụng nguyên prop của ví dụ này cho widget mới.
Tách ID khỏi chữ người dùng nhìn thấy
DropdownButton constructor yêu cầu các item có giá trị phân biệt. Khi value không null, lựa chọn cần khớp một item. DropdownMenuItem.value là giá trị được trả về qua onChanged, còn child có thể hiển thị nhãn khác.
Ví dụ dùng chuỗi ID cho cả item và state được chọn, giúp việc so khớp đơn giản. Không đưa object vừa tạo lại ở mỗi lần tải vào value rồi trông chờ mặc định mọi class đều so bằng nội dung. Nếu ứng dụng dùng object, cần hiểu rõ quy tắc equality của kiểu đó.
Một màn hình có hai nhãn giống nhau
Đặt vào lib/main.dart của dự án Material thử nghiệm:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: TeamPicker()));
class TeamOption {
const TeamOption(this.id, this.label);
final String id;
final String label;
}
class TeamPicker extends StatefulWidget {
const TeamPicker({super.key});
@override
State<TeamPicker> createState() => _TeamPickerState();
}
class _TeamPickerState extends State<TeamPicker> {
static const teams = [
TeamOption('mobile-hn', 'Mobile'),
TeamOption('mobile-hcm', 'Mobile'),
TeamOption('backend', 'Backend'),
];
String? selectedId;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chọn nhóm')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButton<String>(
value: selectedId,
hint: const Text('Chưa chọn nhóm'),
items: teams.map((team) {
return DropdownMenuItem<String>(
value: team.id,
child: Text('${team.label} (${team.id})'),
);
}).toList(),
onChanged: (nextId) {
setState(() => selectedId = nextId);
},
),
Text('ID đã chọn: ${selectedId ?? "chưa có"}'),
],
),
),
);
}
}
Hai dòng Mobile có ID khác nhau; ví dụ hiển thị thêm ID trong menu để người thử phân biệt được. Trong sản phẩm, nên dùng thông tin có ý nghĩa với người dùng như địa điểm hoặc tên đơn vị thay vì bắt họ nhớ mã nội bộ. Điều kiện cần giữ là value ổn định và duy nhất.
Kiểm tra giá trị null và từng lựa chọn
Khi chưa chọn, selectedId bằng null và hint dự định xuất hiện. Chọn lần lượt Mobile ở Hà Nội, Mobile ở TP.HCM và Backend, rồi kiểm tra dòng ID phía dưới. Hai lựa chọn Mobile phải có thể dẫn đến hai ID khác nhau dù cùng label gốc.
Theo DropdownButton.value, null là trạng thái chưa chọn có cách hiển thị riêng. Đừng dùng chuỗi rỗng làm đại diện ngầm cho null nếu danh sách không có item mang giá trị rỗng; điều đó tạo một giá trị khác nhưng vẫn không khớp item nào.
Khi danh sách thay đổi sau một lần tải
Ví dụ giữ danh sách cố định để làm rõ định danh. Với dữ liệu động, một selectedId hợp lệ ở lượt trước có thể biến mất sau khi lọc hoặc tải lại. Trước khi render tập options mới, kiểm tra xem ID cũ còn có đúng một đối tác trong tập ấy không. Nếu không còn, chọn chính sách rõ ràng: xóa lựa chọn về null hoặc yêu cầu người dùng chọn lại.
Nếu backend gửi hai bản ghi trùng ID, không sửa bằng cách chỉ đổi label. Rà nguồn dữ liệu và quyết định bản ghi nào hợp lệ. Tự gắn index vào value có thể làm lựa chọn đổi nghĩa khi sắp xếp lại danh sách, nên nó không thay thế một ID ổn định.
Đọc assertion để khoanh vùng lỗi
Constructor công khai có kiểm tra số item khớp value. Với danh sách không rỗng và value khác null, hãy đếm số item có value bằng lựa chọn hiện tại: 0 gợi ý lựa chọn đã mất hoặc sai kiểu/giá trị; từ 2 trở lên gợi ý định danh bị trùng. Đây là thao tác chẩn đoán dữ liệu, không phải lý do để tắt assertion.
Kế hoạch kiểm tra tiếp theo nên có danh sách rỗng, ID bị xóa, ID trùng và hai label giống nhau nhưng ID khác. Phần sửa cốt lõi là để state lưu định danh của lựa chọn, còn label chỉ phục vụ hiển thị. Nhờ vậy, việc đổi nhãn hoặc dịch giao diện không tự làm đổi thực thể được chọn.


