You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:24:22