Flutter下拉菜单使用Firebase数据时出现重复值断言错误
问题回顾
硬编码Dropdown选项时功能正常,但使用Firebase获取的分类列表时,出现断言错误:DropdownButton的值应对应唯一项,且控制台显示原本5组键值对变为10组,存在同一key对应不同value的重复项问题。
核心错误分析
- 职责混淆导致数据重复:
Category类同时承担数据模型和StateNotifier的职责,每次调用getCategoryLabels时,循环中每次添加新分类都会扩展旧state,重复调用就会导致数据累加翻倍(比如两次调用后变成10项)。 - Value值类型不匹配:
DropdownButtonFormField的value绑定了productCategory(整个state列表),但实际需要的是选中的单个分类ID,类型不匹配触发断言失败。 - 数据加载逻辑低效:
getCategoryLabels在循环遍历Firebase文档时,每次迭代都更新state,应该一次性收集所有分类后再赋值,避免多次不必要的状态更新。 - 选中事件未生效:
categoryOnChanged方法仅返回name,未更新选中状态,导致Dropdown无法同步选中值。
修复方案
1. 拆分数据模型与状态管理
将数据模型和StateNotifier分离,明确职责:
// 独立的分类数据模型 class CategoryModel { final String id; final String name; final String imageUrl; CategoryModel({ required this.id, required this.name, required this.imageUrl, }); } // 状态管理类 final categoryProvider = StateNotifierProvider<CategoryNotifier, List<CategoryModel>>( (ref) => CategoryNotifier(), ); class CategoryNotifier extends StateNotifier<List<CategoryModel>> { String? selectedCategoryId; CategoryNotifier() : super([]); String? get selectedId => selectedCategoryId; void updateSelectedCategory(String? id) { selectedCategoryId = id; // 触发UI更新 state = [...state]; } Future getCategoryLabels() async { final snapshot = await FirebaseFirestore.instance.collection('categories').get(); // 一次性转换所有文档为模型,替换state而非累加 final categories = snapshot.docs.map((doc) => CategoryModel( id: doc.get('categoryId'), name: doc.get('name'), imageUrl: doc.get('image'), )).toList(); state = categories; } }
2. 修正DropdownButtonFormField实现
绑定正确的选中值,修复选项生成逻辑:
child: Consumer<CategoryNotifier>( builder: (context, notifier, child) { return DropdownButtonFormField<String>( dropdownColor: Colors.deepOrange, hint: Text( "Category", style: TextStyle( color: Colors.white, fontSize: Device.screenType == ScreenType.mobile ? 16.sp : 18.sp, ), ), // 绑定选中的分类ID value: notifier.selectedId, decoration: const InputDecoration.collapsed(hintText: ''), icon: const Icon(Icons.arrow_drop_down, color: Colors.white), iconSize: Device.screenType == ScreenType.mobile ? 18.sp : 22.sp, elevation: 16, validator: (value) => value == null ? 'Please select product category' : null, items: notifier.state.map((cat) { return DropdownMenuItem<String>( value: cat.id, child: Row( children: [ Icon( Icons.check, color: Colors.white, size: Device.screenType == ScreenType.mobile ? 16.sp : 20.sp, ), SizedBox(width: 3.w), Text( cat.name, style: TextStyle( color: Colors.white, fontSize: Device.screenType == ScreenType.mobile ? 16.sp : 20.sp, ), ), ], ), ); }).toList(), onChanged: (dropdownValue) { notifier.updateSelectedCategory(dropdownValue); }, ); }, ),
3. 修正手动遍历逻辑(若需保留)
确保基于去重后的分类列表生成选项:
// 从状态管理获取正确的分类列表 final categoryList = ref.watch(categoryProvider); Map<String, Key> dropdownKeys = {}; for (var cat in categoryList) { // 避免重复生成Key dropdownKeys.putIfAbsent(cat.id, () => UniqueKey()); } List<DropdownMenuItem<String>> dropdownMenuItems = categoryList.map((cat) { return DropdownMenuItem<String>( key: dropdownKeys[cat.id], value: cat.id, child: Row( children: [ Icon( Icons.check, color: Colors.white, size: Device.screenType == ScreenType.mobile ? 16.sp : 20.sp, ), SizedBox(width: 3.w), Text( cat.name, style: TextStyle( color: Colors.white, fontSize: Device.screenType == ScreenType.mobile ? 16.sp : 20.sp, ), ), ], ), ); }).toList();
内容的提问来源于stack exchange,提问作者Obisi7
相关产品推荐
相关产品推荐

