I created a method for Dropdown button to reuse the code. Whenever the method is called, the button does not update. But the value updates in the console updates when I tried to debug. Even though I created the method in Stateful widget, setState method doesn't seem to work!
Here's the code and a couple of screenshots
class _SGPAState extends State<SGPA> {
List<String> _credits = ['0', '1', '2', '3', '4', '5'];
List<String> _grades = ['O', 'A+', 'A', 'B+', 'B', 'C', 'P', 'F', 'Ab', 'I'];
List<String?> selectedCredit = List.filled(15, '0');
List<String?> selectedGrade = List.filled(15, 'O');
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('SNo', style: kGPATextStyle),
Text('Credits', style: kGPATextStyle),
Text('Grade', style: kGPATextStyle),
],
),
SizedBox(
height: 15,
),
dataRow(index: 0),
dataRow(index: 1),
dataRow(index: 2),
dataRow(index: 3),
dataRow(index: 4),
],
);
}
Row dataRow({required int index}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${index+1}',
style: kGPATextStyle,
),
buildDropdown(selectedValue: selectedCredit[index], selectedList: _credits),
buildDropdown(selectedValue: selectedGrade[index], selectedList: _grades),
],
);
}
DropdownButton<String> buildDropdown(
{required String? selectedValue, required List<String> selectedList}) {
return DropdownButton(
value: selectedValue,
items: selectedList.map((location) {
return DropdownMenuItem(
value: location,
child: Text(
location,
style: kDropdownTextStyle,
),
);
}).toList(),
onChanged: (String? newValue) {
selectedValue = newValue;
setState(() {
selectedValue = newValue;
});
print(selectedValue);
},
);
}
}
Dropdown buttons screenshot
Console screenshot
I'm stuck with this for quite some time.
Can anyone tell me the reason for this issue? Or is there a solution?
You are assigning to the local variable selectedValue, which isn't persisted from one build to another. So if you want the value to be saved across builds, you have to assign it to an attribute on the state class (like selectedCredit/selectedGrade).
Here's a possible solution to your problem:
Row dataRow({required int index}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${index + 1}',
style: kGPATextStyle,
),
buildDropdown(
selectedValue: selectedCredit[index],
onSelected: (v) => setState(() {
selectedCredit[index] = v;
}),
selectedList: _credits,
),
buildDropdown(
selectedValue: selectedGrade[index],
onSelected: (v) => setState(() {
selectedGrade[index] = v;
}),
selectedList: _grades,
),
],
);
}
DropdownButton<String> buildDropdown({
required final String? selectedValue,
required final ValueSetter<String?> onSelected,
required final List<String> selectedList,
}) {
return DropdownButton(
value: selectedValue,
items: selectedList.map((location) {
return DropdownMenuItem(
value: location,
child: Text(
location,
style: kDropdownTextStyle,
),
);
}).toList(),
onChanged: (String? newValue) {
onSelected(newValue);
},
);
}
I have this Flutter code with two radio buttons in a FormField.
import 'package:flutter/material.dart';
enum Gender { male, female }
class RadioFormField extends FormField<String> {
RadioFormField({
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
String initialValue = '',
AutovalidateMode autovalidateMode = AutovalidateMode.always,
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidateMode: autovalidateMode,
builder: (FormFieldState<String> state) {
String maleRadioButtonTitle = 'Male';
String femaleRadioButtonTitle = 'Female';
String? _genderValue = 'male';
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: RadioListTile<String>(
title: Text(maleRadioButtonTitle),
value: Gender.male.toString(),
groupValue: _genderValue,
onChanged: (String? value) {
state.didChange(_genderValue = 'male');
},
),
),
Expanded(
child: RadioListTile<String>(
title: Text(femaleRadioButtonTitle),
value: Gender.female.toString(),
groupValue: _genderValue,
onChanged: (String? value) {
state.didChange(_genderValue = 'female');
},
),
),
],
);
},
);
}
The problem is after they have rendered none of them respond to a selection. I can not seem to understand what is wrong. What am I missing?
A few things to be fixed here.
First,
state.didChange is a function that takes a single parameter value which is used to update the state of the form with the value provided.
So, call it like this,
onChanged: (String value) {
state.didChange(value);
},
Next, you are always sending male as the groupValue with this groupValue: _genderValue. Since _genderValue is hardcoded to be male all the time.
So change it to this,
groupValue: state.value
Finally, your code will be,
class RadioFormField extends FormField<String> {
RadioFormField({
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
String initialValue = '',
AutovalidateMode autovalidateMode = AutovalidateMode.always,
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidateMode: autovalidateMode,
builder: (FormFieldState<String> state) {
String maleRadioButtonTitle = 'Male';
String femaleRadioButtonTitle = 'Female';
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: RadioListTile<String>(
title: Text(maleRadioButtonTitle),
value: Gender.male.toString(),
groupValue: state.value,
onChanged: (String? value) {
state.didChange(value);
},
),
),
Expanded(
child: RadioListTile<String>(
title: Text(femaleRadioButtonTitle),
value: Gender.female.toString(),
groupValue: state.value,
onChanged: (String? value) {
state.didChange(value);
},
),
),
],
);
},
);
}
Error code
Hi I'm new to flutter and have a question about dropdownbutton regarding using the same values for multiple dropdownbutton.
From my understanding from the error, it was due to using the same list for 2 or more dropdownbuttons in the same activity.
How am i able to resolve this error but still able to reuse the list for 2 or more dropdownbuttons?
String _value1;
String _value2;
final List<String> nameList = <String>[
"Name1",
"Name2",
"Name3",
"Name4",
"Name5",
"Name6",
"Name7",
"Name8"
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text('Hello'),
),
body: ListView(
children: <Widget>[
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value1,
onChanged: (value) {
setState(() {
_value1 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
),
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value2,
onChanged: (value) {
setState(() {
_value2 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
),
],
),
backgroundColor: Colors.grey[200],
);
}
}
I had the exact same error, multiple Dropdowns all feeding from the same static list, the only difference is that in my case, it was a list of Objects, not Strings.
So, if it's a static list, there's no way it's empty, no duplicate values in the list, AND you already make sure value is not empty? Then the only option remaining is that item.value is different than value
In my case, as it was an Object list, I had to overwrite operator == and hashcode methods in my Object class.
bool operator ==(dynamic other) =>
other != null && other is TimeSelection && this.hour == other.hour;
#override
int get hashCode => super.hashCode;
And that was it. I didn't had to initialize _value1 or _value2
You are getting that exception because _value1 and _value2 aren't initialized and providing empty to the dropdown widget.
You could do something like this:
DropdownButton(
value: _value1.isNotEmpty ? _value1 : null, // guard it with null if empty
items: nameList.map((item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
}).toList(),
);
This exception you have because of mistakes:
No _value1 and _value2 initialization.
When you initialize them make sure that _value1 and _value2 right from nameList e.g.
_value1 = nameList[0];
_value2 = nameList[3];
this is important step with complex data type, but in your case
_value1 = "Name1";
_value2 = "Name4";
will be sufficient.
Full example:
String _value1;
String _value2;
final List<String> nameList = <String>[
"Name1",
"Name2",
"Name3",
"Name4",
"Name5",
"Name6",
"Name7",
"Name8"
];
/// initialization is here:
#override
void initState() {
super.initState();
_value1 = nameList[0];
_value2 = nameList[3];
}
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value1,
onChanged: (value) {
setState(() {
_value1 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
),
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value2,
onChanged: (value) {
setState(() {
_value2 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
),
],
);
}
}
I have the same problem, and I solved it.
The dropdown button needs items list and value. We define items and selected items, but the item chosen instance does not inside the items list.
You should try this and fix your logic.
(value ıs selected item value for user)
var _value = itemList.isEmpty
? value
: itemList.firstWhere((item) => item.value == value.value);
More : https://gist.github.com/VB10/fd560694fec0a38751e798a213408001
You must initialise the _value1 and _value2 and make sure those values are also present in nameList.
My solution was more simple than every one else. The fact that was find a value that wasn't the same as in the list, is because I have put a value in the variable, that wasn't either full or empty, the value was this ("") and it has to be null for the Dropdown value instance. So, I just have put a value null in the declaration of variable. like: "String _value;", and voila, it worked.
#Sorry for the English, Brazilian here.
In my case, I use FormBuilderDropdown of the package flutter_form_builder.
Adding key: UniqueKey() in the Widget FormBuilderDropdown is the solution for my case.
I had same issue with Getx package. When it's updated, it causes this error because add same items to list. Adding key: UniqueKey() to DropdownButtonFormField is the solution for me.
You must initialise the _value1 and _value2 with a initial Value.
_value1 and _value2 variables need to be initialized, or you can do that:
value: _value1 != null ? _value1 : null,
hint: Text('Your hint'),
var _issues = [
"Subscription Related",
"Talk Therapy Related",
"Program Related",
"Account Related",
"Technology Related",
];
String _currentSelectedValue=_issues.first;
As much as the answer accepted may be working, it is unnecessary and over achieving.
All you need to do is ENSURE THAT whatever your initialized the value to is in the list as well. That is:
String _value1 = "Name1";
If the initialized value is not in your list, you will get the error message you are getting. Period!
If you are sure that your code is right then do "hot restart" instead of "hot reload".
this solved in my case
value: _value1,
onChanged: (value) {
setState(() {
_value1 = value;
_value2 = null;
});
},
The dropdownbuttonformfield filtering mechanism uses a single field for filtering. It can not filter by class hash. Set the DropdownMenuItem value to the key of the incoming data. The dropdownmenuItem value must be unique. The key is String type. currentStatus holds the key to position in the dropdown. I map a list of class objects where the class has databaseValue field and a displayValue field to the dropdownmenuitem as value:databaseValue and child:Text(displayValue). Now, I can set _currentStatus to a databaseValue and it will position in the dropdown.
String _currentStatus;
List<DropdownMenuItem> listMenuItems =
<DropdownMenuItem<String>>[];
Provider.of<Api>(context, listen: false)
.getComboViews()
.then((data) {
setState(() {
listMenuItems =
data.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item.databaseValue, child: Text(item.displayValue));
}).toList();
DropdownButtonFormField<String>(
value: this._currentStatus,
items: listMenuItems
onChanged: (String value) {
setState(() {
this._currentStatus = value;
});
);
String? dropdownValue
hint: Text( "Select City"),
value: dropdownValue == null ? null : dropdownValue,
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>[
'Islamabad',
'RawalPindi',
'Mangla',
'Mirpur'
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
Once you have Multiple DropDownButtons which are dependent on one another.
Adding key: (_value1 != null)
? Key(_value1)
: UniqueKey() to the dependent DropdownButtonFormField
children: <Widget>[
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value1,
onChanged: (value) {
setState(() {
_value1 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
),
Row(
children: <Widget>[
Text('Name: '),
Center(
child: DropdownButton(
value: _value2,
key: (_value1 != null) ? Key(_value1) : UniqueKey()
onChanged: (value) {
setState(() {
_value2 = value;
});
},
items: nameList.map(
(item) {
return DropdownMenuItem(
value: item,
child: new Text(item),
);
},
).toList(),
),
),
],
You can use the same list in multiple DropDownButton. The error you got is because of having more than one same values in the list.
For Example, if I change the list to given below where I have two items having the same value, it will throw me an error.
`final List<String> nameList = <String>[
"Name1",
"Name1",
"Name3",
"Name4",
"Name5",
"Name6",
"Name7",
"Name8"
];`
Error:
_value1 and _value2 must be in your list
I solved this problem by specifying the type (in my case String): FormBuilderDropdown<String> and setting initialValue: null
I have Flutter 3.3.4
This happens when the value field type is not the same than the types used in items. Here is a example throwing the same error.
FittedBox(
fit: BoxFit.contain,
child: DropdownButton<E>(
//isExpanded: true,
value: box?.get(hiveKey), // hive key = string value --> need to convert to E type
onChanged: (final E? newValue) {
updateSettingsOnTap(box);
},
items: values.map<DropdownMenuItem<E>>((E value) {
return DropdownMenuItem<E>( // --> E type not string
value: value.item,
child: Text(value.getItemValue(), overflow: TextOverflow.ellipsis),
);
}).toList(),
You have to use the same type in value and items fields to fix it. Working code below.
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
FittedBox(
fit: BoxFit.contain,
child: DropdownButton<String>(
//isExpanded: true,
value: box?.get(hiveKey),
onChanged: (final String? newValue) {
updateSettingsOnTap(box);
},
items: values.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value, overflow: TextOverflow.ellipsis),
);
}).toList(),
Here is how I have implemented the dropdowns. This code handles exceptions in case list is not loaded or preselected value does not exist in the list. Also, it does provide validation on selected item.
Following is the Widget implementation ( e.g. froonodropdown.dart)
import 'package:flutter/material.dart';
class FroonoDropDown<T> extends FormField<T> {
final T? selectedValue;
final FocusNode? fnNode;
final void Function(T) onChanged;
final List<T> list;
final String Function(T) getLabel;
final String label;
FroonoDropDown(this.label, this.list, this.selectedValue, this.fnNode, this.onChanged, this.getLabel, {Key? key})
: super(
key: key,
validator: (T? item) {
return item == null ? "Please choose an option" : null;
},
initialValue: list.contains(selectedValue) ? selectedValue : null,
builder: (FormFieldState<T> state) {
List<DropdownMenuItem<T>> dropdownItems = [];
dropdownItems.add(DropdownMenuItem(value: null, child: Text("Select " + label)));
//handle exception in case list is not loaded or selectedValue is not part of the list
T? defaultValue = selectedValue;
if (selectedValue != null && !list.contains(selectedValue)) {
if (getLabel(selectedValue).isEmpty) {
defaultValue = null;
} else {
list.add(selectedValue);
}
}
dropdownItems.addAll(list.map((T item) {
return DropdownMenuItem(
value: item,
child: Text(getLabel(item)),
);
}).toList());
return Column(
children: <Widget>[
InputDecorator(
decoration: InputDecoration(labelText: label),
child: DropdownButtonHideUnderline(
child: DropdownButton(
focusNode: fnNode,
value: defaultValue,
isDense: true,
onChanged: (T? selectedItem) {
state.didChange(selectedItem);
if (selectedItem != null) onChanged(selectedItem);
},
items: dropdownItems),
),
),
if (state.hasError)
Align(
alignment: Alignment.centerLeft,
child: Text(state.errorText!, style: TextStyle(color: Theme.of(state.context).errorColor, fontSize: 12)),
)
],
);
},
);
}
Examples:
With String items:
FroonoDropDown<String>("String Items", stringItemsList, defaultValue, null, (String value) {
defaultValue = value;
//do something else with value;
}, (String item) => item)
With any custom class:
FroonoDropDown<CustomClass>("My Custom List", customClassObjsList,
defaultValue, null, (CustomClass value) {
defaultValue = value;
//do something else with value;
}, (CustomClass act) => act.title)
Make sure you override comparison operator and hashcode in your CustomClass, like this:
#override
bool operator ==(dynamic other) {
return other != null && typeid == other.typeid;
}
#override
int get hashCode => super.hashCode;
I hope above implementation would be useful for you!