Flutter:应该只有一项具有 [DropdownButton] 的值

Flutter: There should be exactly one item with [DropdownButton]'s value

我正在尝试在 Flutter 中创建一个下拉按钮。我从我的数据库中得到一个 列表 然后我将列表传递给我的 dropdownButton 一切正常 数据按预期显示但是 当我从中选择一个元素时我得到这个错误:

There should be exactly one item with [DropdownButton]'s value: Instance of 'Tag'. 
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 805 pos 15: 'items == null || items.isEmpty || value == null ||
          items.where((DropdownMenuItem<T> item) {
            return item.value == value;
          }).length == 1'

我尝试将 DropdownButton 值设置为 null 它起作用了,但是我 看不到所选元素

这是我的代码:

FutureBuilder<List<Tag>>(
    future: _tagDatabaseHelper.getTagList(),
    builder: (BuildContext context, AsyncSnapshot<List<Tag>> snapshot) {
      if (!snapshot.hasData) {
        return Center(
          child: CircularProgressIndicator(),
        );
      }
      return ListView(
        children: <Widget>[
          SizedBox(
            height: MediaQuery.of(context).size.height * 0.2,
          ),
          Container(
            margin: EdgeInsets.symmetric(
                horizontal: MediaQuery.of(context).size.width * 0.07),
            child: Theme(
              data: ThemeData(canvasColor: Color(0xFF525A71)),
              child: DropdownButton<Tag>(
                value: _selectedTag,
                isExpanded: true,
                icon: Icon(
                  Icons.arrow_drop_down,
                  size: 24,
                ),
                hint: Text(
                  "Select tags",
                  style: TextStyle(color: Color(0xFF9F9F9F)),
                ),
                onChanged: (value) {
                  setState(() {
                    _selectedTag = value;
                  });
                },
                items: snapshot.data.map((Tag tag) {
                  return DropdownMenuItem<Tag>(
                    value: tag,
                    child: Text(
                      tag.tagTitle,
                      style: TextStyle(color: Colors.white),
                    ),
                  );
                }).toList(),
                value: _selectedTag,
              ),
            ),
          ),

我使用 futureBuilder 从数据库中获取我的列表

所以我找到了解决方案

创建了空列表来保存我的标签对象

List<Tag> _tagList = [];

然后,在我的 initStateassigned 我从数据库 得到的 list 到上一个列表

     @override
    void initState() {
    super.initState();
    _tagDatabaseHelper.getTagList().then((foo) {
      setState(() {
        _tagList = foo;
      });
    });
  }

最后是我的 DropdownButton 代码:

DropdownButton<Tag>(
            isExpanded: true,
            icon: Icon(
              Icons.arrow_drop_down,
              size: 24,
            ),
            hint: Text(
              "Select tags",
              style: TextStyle(color: Color(0xFF9F9F9F)),
            ),
            items: _tagList.map((foo) {
              return DropdownMenuItem(
                value: foo,
                child: Text(foo.tagTitle),
              );
            }).toList(),
            onChanged: (value) {
              setState(() {
                _selectedTag = value;
              });
            },
            value: _selectedTag,
          ),

好吧,因为没有问题有完全相同的解决方案。我的代码面临同样的问题。这是我如何解决这个问题的。

我的 DropdownButton 代码:

DropdownButton(
   items: _salutations
         .map((String item) =>
             DropdownMenuItem<String>(child: Text(item), value: item))
         .toList(),
    onChanged: (String value) {
       setState(() {
         print("previous ${this._salutation}");
         print("selected $value");
         this._salutation = value;
            });
          },
     value: _salutation,
),

错误

在下面的代码片段中,我为一个字符串类型的选择值设置了状态。现在我的代码的问题是这个选择值的默认初始化。 最初,我将变量 _salutation 初始化为:

String _salutation = ""; //Notice the empty String.

这是一个错误!

初始选择不应为 null 或空,因为错误消息已正确提及。

'items == null || items.isEmpty || value == null ||

因此崩溃:

解决方案
用一些默认值初始化值 object。 请注意 值应该是您的 collection 包含的值之一。 如果不是,则可能会发生崩溃.

  String _salutation = "Mr."; //This is the selection value. It is also present in my array.
  final _salutations = ["Mr.", "Mrs.", "Master", "Mistress"];//This is the array for dropdown

我的下拉代码

child: DropdownButton(
      items: _currencies.map((String value) {
        return DropdownMenuItem<String>(
          child: Text(value),
          value: value,
        );
      }).toList(),
      value: 'Rupees',
      onChanged: (String newValueSelected) {
        // Your code to execute, when a menu item is selected from 
        dropdown
      },
))
var _currencies = ['Rupee','Dollar','Pound'];

我遇到了同样的错误,因为下拉代码块中的值与 _currencies 中的任何字段都不匹配

只需使标签 class 从 Equatable 扩展并将属性传递给道具。这对我有用。

class Tag extends Equatable{
  String id;
  String name;

  Tag(this.id, this.name);

  @override
  List<Object> get props => [id,name];

}

如果尝试使用 class 实例设置下拉列表的值,也可能会出现此错误;

  var tag1 = Tag();
  var tag2 = Tag();
  print(tag1 == tag2); // prints false, dropwdown computes that value is not present among dropdown options

解决这个覆盖运算符 ==:

class Tag{
 String name = "tag";

  @override
  bool operator ==(Object other) => other is Tag && other.name == name;

  @override
  int get hashCode => name.hashCode;
}

或使用https://pub.dev/packages/equatable

class Tag extends Equatable{
 String name = "tag";

  @override
  List<Object> get props => [name];
}

我遇到了同样的问题。解决方案很简单:您必须确保作为默认下拉值的字符串包含在您要在下拉菜单中使用的列表中。如果你想,比方说,使用 api 中的列表,你应该确保至少知道该列表的一个值,以便你可以将它分配给作为默认下拉值的变量。

这里我想显示一个从 api 中获得的列表。为了不出现错误,我将默认下拉值设置为名称“Encajes”,这是我的列表包含的现有类别之一。

String dropdownValue = "Encajes";

    items: categoriesString
    .map<DropdownMenuItem<String>>((String value) {
  return DropdownMenuItem<String>(
    value: value,
    child: Text(value),
  );
}).toList(),

我遇到了同样的问题,令人惊讶的是,我的项目列表中有重复项是从远程数据库中获取的。

每次我从服务器获取数据时(当一个新的应用程序用户登录时),数据没有重复但相同的数据被多次添加到列表中,因为我在多个用户上登录相同的设备。也许你的错误是类似的东西。

因此,请确保在 snapshot.data删除所有重复项,然后再将它们设置为 [=19] 的项目=]DropDownButton.

您可以使用三元运算符避免空值:

  Container(
             child:
              new DropdownButton<String>(
              value: dropdownValue ?? "1",
              icon: const Icon(Icons.arrow_downward),
              iconSize: 24,
              elevation: 16,
              style: const TextStyle(color: Colors.black, fontSize: 18),
              underline: Container(height: 2, color: Colors.white24, ),
              items: <String>['1', '2', '3', '5'].map((String value) {
              return new DropdownMenuItem<String>(
              value: value,
              child: new Text(value),
              );}).toList(),
              onChanged: (value) {
                  setState(() { dropdownValue=value;});
              },
          )),

我用了一招。所选项目成为列表中的第一个索引项目。因此每次更改项目时从列表中删除该项目将该项目作为列表中的第一项重新插入 。请参考以下代码。这里我使用对象作为下拉项,我将小部件作为提取函数。并且在调用 dropDownButton 函数之前 make

//项目列表如下

 List<LeaveType> items = [
 (id=1,name="Sick"),
 (id=2,name="Paid")
 ]

selectedLeave = null;

Row leaveTypeDropDown(StateSetter setCustomState, List<LeaveType> items) {
    if(selectedLeave != null){
      items.remove(selectedLeave);
      items.insert(0, selectedLeave);
    }
    return Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children : [
              text("Select Leave Type",textSize: 15),
              Padding(padding: const EdgeInsets.all(5)),
              Expanded(
                child: Container(
                  padding: const EdgeInsets.only(left: 10.0, right: 10.0),
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.black,width: 1),
                    borderRadius: const BorderRadius.all(Radius.circular(10.0)),
                  ),
                  child: DropdownButtonHideUnderline(
                    child: DropdownButton<LeaveType>(
                      isExpanded: true,
                      //initial value 
                      value: selectedLeave != null ? items[0] : null,
                      icon: const Icon(Icons.arrow_downward),
                      iconSize: 24,
                      elevation: 16,
                      hint: text("Leave Type"),
                      style: const TextStyle(
                          color: Colors.black
                      ),
                      onChanged: (LeaveType  value) {
                        setCustomState(() {
                          selectedLeave = value;
                          items.remove(selectedLeave);
                          items.insert(0, selectedLeave);
                        });
                      },
                      items: items
                          .map((leave) {
                        return  new DropdownMenuItem<LeaveType>(
                          value: leave,
                          child: text(leave.name),
                        );
                      }).toList(),
                    ),
                  ),
                ),
              ),
            ]
        );
  }

我修改如下,解决了:

初始代码:

List<GamesModel> users = <GamesModel>[
  new GamesModel(1,"Option1"),
  new GamesModel(2,"Option2"),

];
return users;

更改代码:

List<GamesModel> users = <GamesModel>[
      const GamesModel(1,"Option1"),
      const GamesModel(2,"Option2"),
];
return users;

如果有人需要我可以把整个代码放上去

          DropdownButton<String>(
            iconEnabledColor: Colors.cyan.withOpacity(.6),
            isExpanded: true,
            itemHeight: 50,
            iconSize: 30,
            hint: Text("Choose Province"),
            items: _provinces
                .map((e) => DropdownMenuItem(
              child: Text(e),
              value: e,
            ))
                .toList(),
            value: _Province,
            onChanged: (String? value) async{
              final respnose=await FirebaseFirestore.instance.collection('city').where('provinceName',isEqualTo: value).get();
              _city=[];
              for(var item in respnose.docs){
                print(item.data());
                _city.add(item.data()['name']);
              }
              
              print(_Province);
              setState(() {
                _city=_city;
                _Province = value;
              });
            },
          ),
          SizedBox(height: 20,),

          DropdownButton<String>(
            iconEnabledColor: Colors.cyan.withOpacity(.6),
            isExpanded: true,
            itemHeight: 50,
            iconSize: 30,
            hint: Text("Choose City"),
            items:_city
                .map((e) => DropdownMenuItem(
              child: Text(e),
              value: e,
            ))
                .toList(),
            value: _City,
            onChanged: (String? value) async{
              setState(() {
                _town=[];
                _Town=null;
              });
              print(_town);
              final respnose=await FirebaseFirestore.instance.collection('town').where('cityName',isEqualTo: value).get();
              print(respnose.docs);


              for(var item in respnose.docs){
                print(item.data());
                _town.add(item.data()['name']);
              }
              print(_town);
              print(_City);
              setState(() {

                _City = value;
                _town=_town;
              });
            },
          ),
          SizedBox(height: 20,),
          
          if(true)
          DropdownButton<String>(
            iconEnabledColor: Colors.cyan.withOpacity(.6),
            isExpanded: true,
            itemHeight: 50,
            iconSize: 30,
            hint: Text("Choose Town"),
            items:_town
                .map((e) => DropdownMenuItem(
              child: Text(e),
              value: e,
            )
            )
                .toList(),
            value: _Town,
            onChanged: (String? value)async {
              print(_Town);
              setState(() {
                _Town = value;
              });

         

如果您忘记为下拉菜单项赋值,也会发生此错误。 ====作品====

<String>['A', 'B', 'C'].map<DropdownMenuItem<String>>((vehicle) {
        print("vehicle is $vehicle");
        print("vehicle is equal ${vehicle == x.value}");
        return DropdownMenuItem<String>(
          value: vehicle,
          child: Text(
            // vehicle.vehicleInfo!.vehicleType!,
            vehicle,
            style: TextStyle(
              color: Colors.grey[600],
            ),
          ),
        );
      }).toList(),

==== 不起作用 ====

<String>['A', 'B', 'C'].map<DropdownMenuItem<String>>((vehicle) {
        return DropdownMenuItem<String>(
          child: Text(
            vehicle,
            style: TextStyle(
              color: Colors.grey[600],
            ),
          ),
        );
      }).toList(),

我有同样的错误,我的默认值不存在于 listItems 中,在下拉按钮中映射为:

String defaultvalue = 'selectCategorie'

const List<String> Subcategories = ['category 1','category 2','category 3'...];

必须更改为:-

String defaultvalue = 'selectCategorie';

const List<String> Subcategories = ['selectCategorie','category 1','category 2','category 3'...];

现在当您在 DropdownButton 中传递 defaultvalue 时没有错误

DropdownButton (
  item:[]
 onChanged: (String values){
   print(values);
setState(() {
defaultValue = values;
});
},
value: defaultValue,
)

我遇到了同样的问题,解决方案是填充DropdownButton的值(值:(使用您设置的项目中的值) 您不能使用任何您想要的值,但它应该是您为 DropdownMenuItem 设置的项目之一。

请注意,如果列表中有重复值,也会出现此错误。 例如,如果 languages = ["English", "English", "French"];

那么如果我设置默认语言=“英语”。

      DropdownButton<String>(
        value: language,
        icon: const Icon(Icons.arrow_downward),
        iconSize: 24,
        elevation: 16,
        style: const TextStyle(color: AppColors.highLightTextColor),
        underline: Container(
          height: 1,
          color: AppColors.underLineColor,
        ),
        onChanged: (String? newValue) async {
          setState(() {
            language = newValue;
          });
        },
        items: languages.map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
      ),

删除重复的值,然后就可以了。

好的,本帖的一些回答一定能帮助您解决问题。但重要的是首先要弄清楚为什么会出现此问题,以及 DropdownButton 对您的期望。 为了让您了解这个问题的一些背景知识,了解如何比较两个 dart 对象实例很重要。

如果您使用原始类型 int、String、bool 等,您很可能不会看到上述错误

这是因为你可以直接比较原始类型,你会得到预期的结果。

例如

int x = 5;
int z = 10;
int y = 5;
String foo= 'hello';
String bar = 'hello; 

x == z; // false
x == y; // true
foo == bar; // true

但是在处理自定义对象时,您必须格外小心,您必须确保覆盖“==”运算符,以便 dart 知道如何比较自定义对象的实例。默认情况下,如果两个对象属于同一实例,则它们是相等的。

考虑标签 class,

class Tag{
final String name;
final String code;

Tag({this.name,this.code});
}
final tag1 = Tag(name:'foo', code: 'hello');
final tag2 = Tag(name:'foo', code: 'hello');

当你比较 tag1 ==tag2 时,dart 会 return false,但它应该 return 正如你所期望的那样。

因此,要处理此问题,您需要重写 == 运算符,如下所示

class Tag{
final String name;
final String code;

Tag({this.name,this.code});

@override
  bool operator ==(Object other){
      return identical(this, other) ||
        (other.runtimeType == runtimeType &&
        other is Tag &&
        other.name == name &&
        other.code == code
   }
}

现在,当您比较 tag1 ==tag2 时,它将 return 为真。

这在官方文档中有记载https://dart.dev/guides/language/effective-dart/design#equality

出现预期的 DropdownButton 错误

  1. 项目不为空
  2. 项目不为空
  3. 值不为空
  4. 值只能在项目中出现一次

如果您使用自定义对象而不覆盖 == 运算符,第 4 点将失败,因此您会收到上述错误。

TLDR;

所以要处理错误,请确保以上 4 点满足并覆盖 == 运算符,以便 dart 可以像您期望的那样比较您的标签 class 的实例。