'Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
I am new to Flutter but I am trying to create a DropdownButtonFormField and it is not working. I am getting an error that says I have duplicate values. What is interesting is that I do not have a list with duplicate values. I have found a similar question on SO and the solution says to initiate the strings with a value and that the user was duplicating a list item, but I have a similar solution for another another list and it seems to work fine. I cannot figure out why it is not working here. Any help is greatly appreciated.
Error Message:
There should be exactly one item with [DropdownButton]'s value: 0.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 1411 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
The relevant error-causing widget was:
StreamBuilder<UserProfile>
ProfileForm Class:
final List<String> accountType = ['Educator', 'School Administrator', 'Parent'];
String _currentAccountType;
@override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserProfile>(
stream: DatabaseService(uid: user.uid).userProfileData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserProfile userProfileData = snapshot.data;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
SizedBox(height: 20.0),
DropdownButtonFormField(
decoration: textInputDecoration,
value: _currentAccountType ?? userProfileData.accountType,
items: accountType.map((accountType) {
return DropdownMenuItem(
value: accountType,
child: Text(accountType),
);
}).toList(),
onChanged: (val) {
setState(() {
_currentAccountType = val;
});
},
),
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userProfileCollection =
Firestore.instance.collection('user_profile');
Future updateUserProfile(
String accountType,
String birthDate,
String courseName,
String dateJoined,
String email,
String firstName,
String lastName,
String schoolName,
String title) async {
return await userProfileCollection.document(uid).setData({
'accountType': accountType,
'birthDate': birthDate,
'courseName': courseName,
'dateJoined': dateJoined,
'email': email,
'firstName': firstName,
'lastName': lastName,
'schoolName': schoolName,
'title': title,
});
}
//User Profile from snapshot
List<Profile> _userProfileListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Profile(
accountType: doc.data['accountType'] ?? '',
birthDate: doc.data['birthDate'] ?? '',
courseName: doc.data['courseName'] ?? '',
dateJoined: doc.data['dateJoined'] ?? '',
email: doc.data['email'] ?? '',
firstName: doc.data['firstName'] ?? '',
lastName: doc.data['lastName'] ?? '',
schoolName: doc.data['schoolName'] ?? '',
title: doc.data['title'] ?? '',
);
}).toList();
}
UserProfile _userProfileFromSnapshot(DocumentSnapshot snapshot) {
return UserProfile(
uid: uid,
accountType: snapshot.data['accountType'],
birthDate: snapshot.data['birthDate'],
courseName: snapshot.data['courseName'],
dateJoined: snapshot.data['dateJoined'],
email: snapshot.data['email'],
firstName: snapshot.data['firstName'],
lastName: snapshot.data['lastName'],
schoolName: snapshot.data['schoolName'],
title: snapshot.data['title'],
);
}
Stream<List<Profile>> get userProfile {
return userProfileCollection.snapshots().map(_userProfileListFromSnapshot);
}
Stream<UserProfile> get userProfileData {
return userProfileCollection
.document(uid)
.snapshots()
.map(_userProfileFromSnapshot);
}
}
Solution 1:[1]
userProfileData.accountType is '0', not 'Educator' or 'School Administrator' or 'Parent'.
Success: value must in items.value
final List<String> accountType = ['Educator', 'School Administrator', 'Parent'];
DropdownButtonFormField(
decoration: textInputDecoration,
value: accountType[0],
items: accountType.map((accountType) {
return DropdownMenuItem(
value: accountType,
child: Text(accountType),
);
}).toList(),
onChanged: (val) {
setState(() {
_currentAccountType = val;
});
},
),
Failed: There should be exactly one item with [DropdownButton]'s value: hahaha
final List<String> accountType = ['Educator', 'School Administrator', 'Parent'];
DropdownButtonFormField(
decoration: textInputDecoration,
value: 'hahaha',
items: accountType.map((accountType) {
return DropdownMenuItem(
value: accountType,
child: Text(accountType),
);
}).toList(),
onChanged: (val) {
setState(() {
_currentAccountType = val;
});
},
),
Solution 2:[2]
It seems that there is some clash when using 'hint:' and 'value:' simultaneously. My approach was to add a global _selected in the Widget's State:
bool _selected;
and then in the DropdownButton itself:
value: _selected ? _userChoice: null,
In this way you actually use one of the item values once they are set.
Here is an entire example taken from some traning code (excuse the silliness ;)
class FavoriteCity extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _FavoriteCityState();
}
}
class _FavoriteCityState extends State<FavoriteCity> {
String nameCity = '';
String _loveLevel = '';
bool _selected = false;
var _howMuchLoved = ['A little', 'So so', 'Quite a bit', 'A lot', 'Greatly'];
@override
Widget build(BuildContext context) {
print('Widget Built');
return Scaffold(
appBar: AppBar(
title: Text('Favorite city app'),
elevation: 8.0,
),
body: Container(
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(
onSubmitted: (String userInput) {
setState(() {
print('State rebuilt');
nameCity = userInput;
});
},
),
DropdownButton<String>(
hint: Text('How much do you love the city?'),
items: _howMuchLoved.map((String myMenuItem) {
return DropdownMenuItem<String>(
value: myMenuItem,
child: Text(myMenuItem),
);
}).toList(),
onChanged: (String valueSelectedByUser) {
_dropDownItemSelected(valueSelectedByUser);
},
value: _selected ? _loveLevel : null,
isDense: true,
),
Padding(
padding: EdgeInsets.all(20.0),
child: Text(
'Your favorite city is $nameCity ! \n ... and you love it $_loveLevel',
style: TextStyle(
fontSize: 20.0,
fontStyle: FontStyle.italic,
),
),
),
],
),
),
);
}
void _dropDownItemSelected(String valueSelectedByUser) {
setState(() {
this._loveLevel = valueSelectedByUser;
_selected = true;
});
}
Solution 3:[3]
In my case, below change resolved the error:
Error scenario:
var itemList=['Alpha','Beta','Cat'];
var itemSelected='Zebra';
Working scenario:
var itemList=['Alpha','Beta','Cat'];
var itemSelected='Cat'; //Can be Alpha, Beta or Cat but not any other value
Dropdown widget code:
DropdownButton<String>(
items: itemList.map((String singleItem){
return DropdownMenuItem<String>(
value: singleItem,
child:Text(singleItem)
);
}).toList(),
onChanged: (String itemChosen){
setState(() {
this.itemSelected=itemChosen;
});
},
value:itemSelected ,
),
Having the 'itemSelected' variable value same as one of the elements in the list resolved the problem for me.
Solution 4:[4]
I also encountered this case, but there is no workaround for you to help me see where the error code is?
var dataLv2 = new List<DropdownMenuItem<String>>();
var dataLv3 = new List<DropdownMenuItem<String>>();
Widget widgetLoaiViPham() {
return StreamBuilder(
initialData: "KHONG",
stream: widget.bloc.subject_ketluan_cmis_loai_vipham_stream,
builder: (context, snapshot) {
return DropdownButtonFormField(
value: snapshot.data,
items: getDropDownMenuLv1(),
decoration: InputDecoration(labelText: "Lo?i vi ph?m"),
onChanged: (newVal) {
dataLv2 = defaultVal2(newVal);
var tempVal2 = dataLv2.length > 0 ? dataLv2.first.value : null;
dataLv3 = defaultVal3(tempVal2);
var tempVal3 = dataLv3.length > 0 ? dataLv3.first.value : null;
widget.bloc.subject_ketluan_cmis_loai_vipham_sink.add(newVal);
widget.bloc.subject_ketluan_cmis_loai_kiemtra_sink.add(tempVal2);
widget.bloc.subject_ketluan_cmis_loai_xuly_sink.add(tempVal3);
},
);
},
);
}
Widget widgetLoaiKiemTra() {
return StreamBuilder(
initialData: null,
stream: widget.bloc.subject_ketluan_cmis_loai_kiemtra_stream,
builder: (context, snapshot) {
return DropdownButtonFormField(
value: null,
items: dataLv2,
decoration: InputDecoration(labelText: "Lo?i ki?m tra"),
onChanged: (newVal) {
dataLv3 = defaultVal3(newVal);
var tempVal3 = dataLv3.length > 0 ? dataLv3.first.value : null;
widget.bloc.subject_ketluan_cmis_loai_kiemtra_sink.add(newVal);
widget.bloc.subject_ketluan_cmis_loai_xuly_sink.add(tempVal3);
},
);
},
);
}
When changing the widgetLoaiViPham, the data of the widgetLoaiKiemTra will change according to everything is normal, but when I change the widgetLoaiKiemTra and then I continue to change the widgetLoaiViPham, an error will occur.
Although debugging, the value and items of widgetLoaiKiemTra match
Solution 5:[5]
In my case: my list had duplicated data which presents me with this error, idk know it gets duplicated along the way in my app, so before displaying the dropdown button I just cleared my list and refill the list before passing it onto dropdown. if this type of error occurs, crosscheck this solution too.
Solution 6:[6]
Ended up here after facing the same issue with a Dropdown list of custom objects, even though the populated list did not contain any duplicates (in terms of data).
The resolution for me was to override the ==
operator and hashCode
function in the class of the aforementioned objects.
Example (omitted some irrelevant internal functions for higher visibility):
import 'dart:convert';
import 'package:quiver/core.dart';
class PrefectureDto{
String id = ""; // Every prefecture is given a unique id from the DEDDHE API. This id is used for the requests.
String name = ""; // The name of each prefecture.
/// Constructor that sets all the fields of the Prefecture model.
PrefectureDto(this.id, this.name);
...internal methods etc...
@override
bool operator ==(Object other){
PrefectureDto o = other as PrefectureDto;
if(o.id == id) return true;
return false;
}
@override
int get hashCode => hash2(id.hashCode, name.hashCode);
}
Implementation of the dropdown, for keeping my answer consistent:
class _PrefecturesDropdownState extends State<PrefecturesDropdown> {
late PrefectureDto defaultPrefecture;
List<PrefectureDto> prefectures = List<PrefectureDto>.empty(growable: true);
late final Future? prefecturesFuture = _getPrefectures();
/// Performs a request to the DEDDHE website and extracts
/// the prefectures from the HTML.
Future<List<PrefectureDto>> _getPrefectures() async {
Response response = (await Rest.doGET(
"URL", {}));
setState(() {
if (prefectures.isEmpty){
prefectures = PrefecturesHandler.extract(response.body);
}
defaultPrefecture = prefectures.firstWhere((element) => element.id == "10");
});
return prefectures;
}
Widget prefecturesDropdown(BuildContext context) {
return DropdownButton<PrefectureDto>(
value: defaultPrefecture,
icon: const Icon(Icons.keyboard_arrow_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
underline: Container(
height: 2,
color: const Color(0xFFB00020),
),
onChanged: (PrefectureDto? newValue) {
setState(() {
defaultPrefecture = newValue!;
});
},
items: prefectures.map<DropdownMenuItem<PrefectureDto>>((PrefectureDto value) {
return DropdownMenuItem<PrefectureDto>(
value: value,
child: Text(value.name),
);
}).toList(),
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: prefecturesFuture,
builder: (context, snapshot) {
return prefecturesDropdown(context);
});
}
}
Full code on GitHub
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | NBM |
Solution 4 | LĂȘ V?n ???c |
Solution 5 | Anas Yousuf |
Solution 6 | Ioannis Brant-Ioannidis |