Flutter跨页面复用DropDown组件值重复报错解决方法
问题描述
复用自定义下拉选择组件时触发运行时报错:
There should be exactly one item with [DropdownButton]'s value: blue. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
报错含义:DropdownButton当前选中值blue需要唯一匹配1个下拉菜单项,当前检测到0个匹配项,或存在2个及以上DropdownMenuItem绑定了重复的value值。
故障根因
- 菜单项value绑定错误:遍历选项列表生成
DropdownMenuItem时,所有菜单项的value都被错误赋值为组件初始传入的dropdownValue,导致全部菜单项value重复,直接触发重复值校验规则。 - 组件类型选型错误:将需要维护选中状态的下拉组件定义为
StatelessWidget(无状态组件),dropdownValue被声明为final属性本就不支持运行时修改,即使强行赋值也不会触发组件重绘,选中值更新后UI无法同步刷新,同时父组件维护的选中状态也无法同步,易出现值不匹配问题。 - 缺少初始值合法性校验:未判断传入的初始选中值是否存在于当前渲染的选项列表中,若传入值不在列表内会触发0匹配报错。
修复实现
修复后可跨页面复用的组件代码如下:
class DropDown extends StatefulWidget { // 初始选中值 final String? initialValue; // 下拉类型:传"category"加载分类列表,其他值加载颜色列表 final String itemsList; // 绑定的文本控制器,选中后自动同步值 final TextEditingController textController; // 选中值变化回调,父组件需要监听变化时可传入 final ValueChanged<String?>? onChanged; const DropDown({ super.key, this.initialValue, required this.itemsList, required this.textController, this.onChanged, }); @override State<DropDown> createState() => _DropDownState(); } class _DropDownState extends State<DropDown> { // 当前选中值 String? _selectedValue; // 分类选项列表 final List<String> categoryList = const [ "Hoodie", "Shoes", "Jeans", "Shirts", ]; // 颜色选项列表 final List<String> colorList = const [ "pink", "red", "orange", "yellow", "lightgreen", "green", "turquise", "blue", "darkblue", "purple", "grey", "brown", "black", "white" ]; @override void initState() { super.initState(); // 初始化时校验初始值合法性,避免0匹配报错 final currentList = widget.itemsList == "category" ? categoryList : colorList; if (widget.initialValue != null && currentList.contains(widget.initialValue)) { _selectedValue = widget.initialValue; widget.textController.text = _selectedValue!; } else { _selectedValue = null; widget.textController.clear(); } } @override Widget build(BuildContext context) { final List<String> items = widget.itemsList == "category" ? categoryList : colorList; return DropdownButtonFormField<String>( value: _selectedValue, isExpanded: true, icon: const Icon(Icons.arrow_downward), elevation: 8, style: const TextStyle(color: Colors.grey, fontSize: 16), onChanged: (String? newValue) { setState(() { _selectedValue = newValue; }); // 同步值到绑定的文本控制器 if (newValue != null) { widget.textController.text = newValue; } else { widget.textController.clear(); } // 触发父组件传入的变化回调 widget.onChanged?.call(newValue); }, items: items.map<DropdownMenuItem<String>>((String itemValue) { return DropdownMenuItem<String>( // 核心修复:绑定当前遍历到的选项值,禁止使用全局初始值 value: itemValue, child: Padding( padding: const EdgeInsets.only(left: 12.0), child: Text(itemValue), ), ); }).toList(), ); } }
调用方式
在任意页面使用该组件时,只需传入对应参数即可:
// 父页面中声明状态和控制器 String? dropdownValueCategory; final TextEditingController categoryController = TextEditingController(); // 组件渲染 DropDown( initialValue: dropdownValueCategory, itemsList: "category", textController: categoryController, // 可选:监听选中值变化 onChanged: (val) { dropdownValueCategory = val; }, )
复用说明
- 传入
itemsList: "category"时渲染商品分类下拉,传入其他值时渲染颜色下拉,无需在各页面重复编写选项渲染逻辑 - 组件会自动校验传入的初始值是否合法,若初始值不在对应选项列表内会默认置空,从根源避免0匹配、重复值报错
- 选中值会自动同步到绑定的
TextEditingController,父组件可直接通过控制器获取选中值,无需额外手动赋值
内容的提问来源于stack exchange,提问作者jansemrau
相关产品推荐
相关产品推荐

