DropdownButton not working as expected in Flutter - flutter

Am new to Flutter Development. Am populating a snapshot data received from API in DropdownButtonlist. everything works fine. but when I change the items in the list I get above error. am not sure which one is causing the pbm. i surfed the net a lot but could not find the solution. I get the error even if I have one item in the list. I get error "Error: Either zero or 2 or more [DropdownMenuItem]s were detected with the same value in flutter "
Thanks in advance
FutureBuilder(
future:Api.getSchemes(context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Color(0xffF3D876),
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton<ClsSchemes>(
alignment: AlignmentDirectional.centerStart,
isExpanded: true,
value: dropDownValue,
hint: Text(dropDownValue.Scheme_Name ?? 'Make a selection'),
items: snapshot.data.map<DropdownMenuItem<ClsSchemes>>((item) {
return DropdownMenuItem<ClsSchemes>(
value: item,
child: Text(item.Scheme_Name),
);
}).toList(),
onChanged: (value) {
setState(() {
dropDownValue = value!;
TotalDues = value.Total_Dues;
});
},
),
)
: Container(
child: Center(
child: Text('Loading...'),
),
);
},
),

Since you are using an instance of ClsSchemes as your value, you need to make sure that operator == actually works properly for your class.
For this you need to override the == operator and the hashCode:
Example take from here
class Example {
final int value;
Example(this.value);
#override
bool operator ==(Object other) =>
other is Example &&
other.runtimeType == runtimeType &&
other.value == value;
#override
int get hashCode => value.hashCode;
}
You need to find out what the identifying value of your class is. When are two of those classes equal?
If you already have specific fields that are your "identity" fields, the package equatable makes it a little easier.

Related

Expected a value of type 'String', but got one of type 'List<dynamic>' for my DropdownMenu

Got an API call that returns a bunch of data for my app. This particular data set is a Map<String, List<dynamic>>, I'm processing this data to make it usable within my app and passing it around to necessary widgets. I came across his error which makes no sense to me but it is self-explanatory looking at the code I cant see anything.
This code is a part of a bigger code please comment if you want me to add it as it just takes in a few arguments to process the Future and create the Map<String, List<dynamic>>.
This is the code where the error is being thrown (Line:45)
#override
Widget build(BuildContext context) {
return FutureBuilder<Map<String, List<dynamic>>>(
future: options,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data!.values.length,
itemBuilder: ((context, index) {
return DropdownMenu(items: snapshot.data!.values.toList()); //Line: 45
}),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return const CircularProgressIndicator();
}
},
);
}
This is my DropdownMenu Class
class DropdownMenu extends StatefulWidget {
DropdownMenu({super.key, required this.items});
List<dynamic> items;
#override
State<DropdownMenu> createState() => _DropdownMenuState(items);
}
class _DropdownMenuState extends State<DropdownMenu> {
_DropdownMenuState(this.items);
String? value;
List<dynamic> items;
#override
void initState() {
super.initState();
widget.items = items;
}
#override
Widget build(BuildContext context) {
return Container(
width: 300,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black, width: 2)),
child: DropdownButtonHideUnderline(
child: DropdownButton<dynamic>(
value: value,
onChanged: (value) => setState(() => this.value = value),
items: items.map(buildMenuItem).toList(),
),
),
);
}
DropdownMenuItem<dynamic> buildMenuItem(dynamic item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
);
}
Error
The following TypeErrorImpl was thrown building DropdownMenu(dirty, state:
_DropdownMenuState#69c5b):
Expected a value of type 'String', but got one of type 'List<dynamic>'
The relevant error-causing widget was:
DropdownMenu
DropdownMenu:file:///C:/Main%20Storage/_GitHub%20Repos/flutter_fontend_client/lib/components/options__dropdown_menu.dart:45:22
After some debugging... I added this piece of code
var result1 = {
for (var value in snapshot.data!.values.toList())
value.first: value
};
print("Values of the snapshot: $result1");
The result is a big awkward and IDK why it like this. It prints out a json style format string {'key': ['keyStrings', 'keyStrings']
Got a different answer from someone in NorDev Discord.
I will show the answer here + keep the accepted answer as both work and I think that people will appreciate that there is multiple ways of solving this.
return DropdownMenu(items: snapshot.data!.values.elementAt(index));
According to your code, your response is a Map with strings as keys and List as values. That means that snapshot.data!.values.toList() is a list with (possibly) multiple List<dynamic> that you are passing to DropdownMenu.
DropdownMenu expects that the elements of the list are of type String but they are not.
I suspect what you want to do is actually get the first list, so you could do
return DropdownMenu(items: snapshot.data!.values.first);

Flutter QR how to pass QR data to the next screen

How do I make it so when the user scan a QR code, the result will then be passed to the next screen.
Here is my code so far,
Widget build(BuildContext context) => SafeArea(
child: Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
buildQrView(context),
Positioned(top: 10, child: buildControlButtons()),
Positioned(bottom: 30, child: buildResult()),
],
),
),
The buildResult is this
Widget buildResult() => Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.white24),
child: Text(
barcode != null ? _dataFetch() : 'Scan a code!',
maxLines: 3,
),
Then the function _dataFetch is as below
_dataFetch() async {
if (barcode == null) {
print('error');
} else {
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new TransferProcessQR(
value: PassdataQR(
email: barcode!.code.toString(),
)
)
);
Navigator.of(context).push(route);
}
I have another class for PassdataQR but its pretty self explanatory. With this code everytime I run it will give me an error
The following _TypeError was thrown building QRScanPage(dirty, dependencies: [MediaQuery], state: _QRScanPageState#720ae):
type 'Future' is not a subtype of type 'String'
and the Navigator functions will be messed up.
Is there another approach I can do, so after a QR code is scanned, the result will be passed to the next screen without errors?
It seems to me that your _dataFetch method returns a futureand in your buildResult method you're using it like so:
Text(
barcode != null ? _dataFetch() : 'Scan a code!',
maxLines: 3,
)
You can use a futurebuilder to retrieve the async data:
Widget buildResult() => Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.white24),
child: FutureBuilder<string>(
future: _dataFetch,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.HasData) {
return Text(snapshot.data, maxLines: 3);
} else return Text('Scan a code!', maxLines: 3);
},
),
);
According to your repository you could just modify line 150:
controller.scannedDataStream
.listen((barcode) => {
setState(() => this.barcode = barcode));
Get.to(new TransferProcessQR(
value: PassdataQR(
email: barcode!.code.toString(),
)
));
}
Notice that in order for this to work you'll have to use the Get Package Route Management to navigate to another page. That's because you don't have access to the build context in this code snipped. Normally you would call Navigator.of(context).push(...) but that's not possible without a build context.

Flutter Hooks Riverpod not updating widget despite provider being refreshed

I've been studying flutter for a couple of months and I am now experimenting with Hooks and Riverpod which would be very important so some results can be cached by the provider and reused and only really re-fetched when there's an update.
But I hit a point here with an issue where I can't wrap my head around the provider update to reflect in the Widget. Full example can be checked out from here -> https://github.com/codespair/riverpod_update_issue I've added some debug printing and I can see the provider is properly refreshed but the changes don't reflect on the widget.
The example has a working sample provider:
// create simple FutureProvider with respective future call next
final futureListProvider =
FutureProvider.family<List<String>, int>((ref, value) => _getList(value));
// in a real case there would be an await call inside this function to network or local db or file system, etc...
Future<List<String>> _getList(int value) async {
List<String> result = [...validValues];
if (value == -1) {
// do nothing just return original result...
} else {
result = []..add(result[value]);
}
debugPrint('Provider refreshed, result => $result');
return result;
}
a drop down list when changed refreshes the provider:
Container(
alignment: Alignment.center,
padding: EdgeInsets.fromLTRB(5, 2, 5, 1),
child: DropdownButton<String>(
key: UniqueKey(),
value: dropDownValue.value.toString(),
icon: Icon(Icons.arrow_drop_down),
iconSize: 24,
elevation: 16,
underline: Container(
height: 1,
color: Theme.of(context).primaryColor,
),
onChanged: (String? newValue) {
dropDownValue.value = newValue!;
context
.refresh(futureListProvider(intFromString(newValue)));
},
items: validValues
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: Theme.of(context).primaryTextTheme.subtitle1,
),
);
}).toList(),
),
),
And a simple list which uses the provider elements to render which despite the provider being properly refreshed as you can see in the debugPrinting it never updates:
Container(
key: UniqueKey(),
height: 200,
child: stringListProvider.when(
data: (stringList) {
debugPrint('List from Provider.when $stringList');
return MyListWidget(stringList);
// return _buildList(stringList);
},
loading: () => CircularProgressIndicator(),
error: (_, __) => Text('OOOPsss error'),
),
),
]),
class MyListWidget extends HookWidget {
final GlobalKey<ScaffoldState> _widgetKey = GlobalKey<ScaffoldState>();
final List<String> stringList;
MyListWidget(this.stringList);
#override
Widget build(BuildContext context) {
debugPrint('stringList in MyListWidget.build $stringList');
return ListView.builder(
key: _widgetKey,
itemCount: stringList.length,
itemBuilder: (BuildContext context, int index) {
return Card(
key: UniqueKey(),
child: Padding(
padding: EdgeInsets.all(10), child: Text(stringList[index])),
);
},
);
}
As I am evaluating approaches to develop some applications I am getting inclined to adopt a more straightforward approach to handle such cases so I am also open to evaluate simpler, more straightforward approaches but I really like some of the features like the useMemoized, useState from hooks_riverpod.
One thing I wanted to note before we get started is you can still use useMemoized, useState, etc. without hooks_riverpod, with flutter_hooks.
As far as your problem, you are misusing family. When you pass a new value into family, you are actually creating another provider. That's why your list prints correctly, because it is, but the correct result is stuck in a ProviderFamily you aren't reading.
The simpler approach is to create a StateProvider that you use to store the currently selected value and watch that provider from your FutureProvider. It will update the list automatically without having to refresh.
final selectedItemProvider = StateProvider<int>((_) => -1);
final futureListProvider = FutureProvider<List<String>>((ref) async {
final selected = ref.watch(selectedItemProvider).state;
return _getList(selected);
});
DropdownButton<String>(
...
onChanged: (String? newValue) {
dropDownValue.value = newValue!;
context.read(selectedItemProvider).state = intFromString(newValue);
},
}

Flutter: Prevent executed feturebuilder when setState is occurred

I am trying to load DropDownMenu inside Future builder.In my widget i have a Column. Inside Column I have a few widget :
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(),
Divider(),
Container(),
...widget._detailsModel.data.appletActions.map((item) {
.....
...item.appletInputs.map((inputs) {
FutureBuilder(
future: MyToolsProvider()
.getDropDownConfiges(inputs.dataUrl),
builder:
(ctx,AsyncSnapshot<DropDownModel.DropDownConfigToolsModle>snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState ==
ConnectionState.done) {
_dropDown = snapshot.data.data[0];
return DropdownButton<DropDownModel.DataModle>(
hint: Text("Select Item"),
value: _dropDown,
onChanged: (data) {
setState(() {
_dropDown = data;
});
},
items: snapshot.data.data.map((item) {
return DropdownMenuItem<
DropDownModel.DataModle>(
value: item,
child: Row(
children: <Widget>[
Icon(Icons.title),
SizedBox(
width: 10,
),
Text(
item.title,
style: TextStyle(
color: Colors.black),
),
],
),
);
}).toList(),
);
} else {
return Center(
child: Text('failed to load'),
);
}
}),
}
}
]
As you can see i have FutureBuilder inside a loop to show DropdownButton.everything is ok and code works as a charm but my problem is :
onChanged: (data) {
setState(() {
_dropDown = data;
})
every time setState called, future: MyToolsProvider().getDropDownConfiges(inputs.dataUrl), is executed and
_dropDown = snapshot.data.data[0]; again initialized and it get back in a first time .
It is not possible declared MyToolsProvider().getDropDownConfiges(inputs.dataUrl), in initState() method because inputs.dataUrl it is not accessible there.
How can i fixed that?
Updating parent state from within a builder is anti-pattern here. To reduce future errors and conflicts I recommend to wrap the parts that use and update _dropDown variable as a statefull widget.
Afterward the builder is just responsible of selecting correct widget based on future results and separated widget will only update itself based on interactions. Then hopefully many current and potential errors will disappear.
Do one thing, change this
_dropDown = snapshot.data.data[0];
to
_dropDown ??= snapshot.data.data[0];
What this will do is, it will check if _dropDown is null then assign it with value otherwise it won't.

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

I am trying to create a dropdown button in Flutter. I am getting a List from my database then I pass the list to my dropdownButton everything works the data is shown as intended but when I choose an element from it I get this error:
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'
I tried setting DropdownButton value to null it works but then I can't see the chosen element.
Here is my code:
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,
),
),
),
I used futureBuilder to get my List from database.
Well, since no problem has an exact same solution. I was facing the same issue with my code. Here is How I fixed this.
CODE of my 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,
),
The Error
In the code snippet below, I am setting the state for a selection value, which is of type String. Now problem with my code was the default initialization of this selection value.
Initially, I was initializing the variable _salutation as:
String _salutation = ""; //Notice the empty String.
This was a mistake!
Initial selection should not be null or empty as the error message correctly mentioned.
'items == null || items.isEmpty || value == null ||
And hence the crash:
Solution Initialize the value object with some default value. Please note that the value should be the one of the values contained by your collection. If it is not, then expect a crash.
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
Might also get this error if trying to set value of dropdown with a class instance;
var tag1 = Tag();
var tag2 = Tag();
print(tag1 == tag2); // prints false, dropwdown computes that value is not present among dropdown options
To solve this override operator ==:
class Tag{
String name = "tag";
#override
bool operator ==(Object other) => other is Tag && other.name == name;
#override
int get hashCode => name.hashCode;
}
or use https://pub.dev/packages/equatable lib
class Tag extends Equatable{
String name = "tag";
#override
List<Object> get props => [name];
}
I had the same problem. The solution is simple: you have to be sure that the String that is your default dropdownvalue is contained in the list that you want to use in your dropdownmenu. If you wanted to, let’s say, use a list from an api, you should be sure to know at least one value of that list, so that you could assign it to the variable that is your default dropdownvalue.
Here I want display a list that I obtain from an api. In order to not obtain the error, I set my defaultdropdownvalue with the name ‘Encajes’ that is one of the existing categories that my list contains.
String dropdownValue = "Encajes";
items: categoriesString
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
Code of my 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'];
I faced same error because the value in the dropdown code block is not matching with any of the fields in _currencies
Okay, some of the answers on this thread will definitely help you resolve the issue. But it is important to clarify why this issue occurs in the first place and what the DropdownButton expects from you.
To give you a little background on the issue it is important to understand how two instances of dart objects are compared.
You will very likely not see the above error if your DropdownButton is dealing with a List of int, String, bool, etc.
This is because you can directly compare primitive types and you would get the expected result.
for instance
int x = 5;
int z = 10;
int y = 5;
String foo= 'hello';
String bar = 'hello;
x == z; // false
x == y; // true
foo == bar; // true
But when dealing with Custom Objects you have to be extra careful and you must ensure you override the "==" operator so that dart knows how to compare instances of your custom object. By default, two objects are equal if they are of the same instance.
consider the Tag 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');
Tag tag3 = tag1;
when you compare
tag3==tag1 dart would return true as expected, But when you compare tag1 == tag2, the dart would return false, since both objects are not of the same instance.
So to deal with this issue you need to override the == operator as shown below
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
}
}
Now when you compare tag1 ==tag2 it would return true.
This is documented in the official docs here https://dart.dev/guides/language/effective-dart/design#equality
Coming to the DropdownButton error it expects
items is not null
items is not empty
value is not null
value must be present only once in items
Point 4 would fail if you are using Custom objects without overriding the == operator and hence you would get the above error.
TLDR;
So to deal with the error, ensure the above 4 points satisfy and override the == operator so that dart can compare instances of your Tag class as you would expect.
just make the tag class extend from Equatable and pass the attributes to the props.. this did the trick for me.
class Tag extends Equatable{
String id;
String name;
Tag(this.id, this.name);
#override
List<Object> get props => [id,name];
}
I have had the same issue and surprisingly, there were duplicates in my list of items which were being fetched from a remote DB.
Each time I fetched the data from the server (when a new app user logged in), the data had no duplicates but the same data was being added to the list multiple times because I was logging in multiple users on the same device. Maybe your bug is something similar.
So, make sure you remove any duplicates in the snapshot.data before setting them as items of the DropDownButton.
i had the same Error and my default value was not present in the listItems was mapping in the Dropdown Button as :
String defaultvalue = 'selectCategorie'
const List<String> Subcategories = ['category 1','category 2','category 3'...];
Had to Change to this :-
String defaultvalue = 'selectCategorie';
const List<String> Subcategories = ['selectCategorie','category 1','category 2','category 3'...];
now when you pass the defaultvalue in the DropdownButton no errors
DropdownButton (
item:[]
onChanged: (String values){
print(values);
setState(() {
defaultValue = values;
});
},
value: defaultValue,
)
I used a trick. The selected item make as first index item in the list .So when changing item at every time remove the item from list and reinsert the item as first item in the list . Please refer the below code. Here iam using Object as the drop down item and the widget i make it as extracted function. and also before calling the dropDownButton function make
//items list like below
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(),
),
),
),
),
]
);
}
I changed as below and it got solved:
Initial Code:
List<GamesModel> users = <GamesModel>[
new GamesModel(1,"Option1"),
new GamesModel(2,"Option2"),
];
return users;
Changed Code:
List<GamesModel> users = <GamesModel>[
const GamesModel(1,"Option1"),
const GamesModel(2,"Option2"),
];
return users;
If anybody want i can put the whole code
Note that if the list has duplicated values, it will also has this error.
For example, if languages = ["English", "English", "French"];
then if I set the default language = "English".
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(),
),
Remove the duplicate values, then it works.
So I found a solution.
I created empty List to hold my Tag objects.
List<Tag> _tagList = [];
Then, in my initState i assigned the list i get from database to the previous List
#override
void initState() {
super.initState();
_tagDatabaseHelper.getTagList().then((foo) {
setState(() {
_tagList = foo;
});
});
}
Finally My DropdownButton code :
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,
),
In my case, i use empty String for default
value : dropdownValue != "" ? dropdownValue : null
Like this, errors be gone
The exact answer is:
keep "value" null before user selection:
String selectedValue = '';
And in the DropdownButton2 Widget:
...
value: selectedValue.isEmpty ? null : selectedValue,
...
It says if selectedValue is empty then give null but when user select a value then give selectedValue
you can avoid the null value using a ternary operator:
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;});
},
)),
This error also occurs if you forget to give dropdown menu items a value.
==== WORKS ====
<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(),
==== DOESNT WORK ====
<String>['A', 'B', 'C'].map<DropdownMenuItem<String>>((vehicle) {
return DropdownMenuItem<String>(
child: Text(
vehicle,
style: TextStyle(
color: Colors.grey[600],
),
),
);
}).toList(),
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;
});
I had the same problem, and the solution is to fill the value of DropdownButton(value: (use a value from the items you set)
you can not use any value you want, but it should be one of the items that you set for the DropdownMenuItem.
I think because of the update in the framework, the error came out
Here is how you can solve it
DropdownButton(
hint: const Text("Please choose your gender"),
items: <String>["Male", "Female", "Rather not say"]
.map<DropdownMenuItem<String>>((e) {
return DropdownMenuItem<String>(
value: e, child: Text(e.toString()));
}).toList(),
onChanged: (String? value) {
setState(() {
dropdownValue = value!;
});
});
Note that: dropdownValue is a string variable defined at the top
If you are loading the list from an api that returns list, look at what i did to debug the error.
Created a reusable widget that handle future response
Widget rangeLists(selectedValue) {
return FutureBuilder(
future: YourFuture,//this should return Future<List>
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading...');
} else {
List<DropdownMenuItem<String>> categoriesItems = [
DropdownMenuItem(
child: Text(selectedValue),
value: selectedValue,
),
];
print('categoriesItems.last.value');
print(categoriesItems.last.value);
var snapshotAsMap = snapshot.data as List;
for (int i = 0; i < snapshotAsMap.length; i++) {
if (snapshotAsMap[i]['category'] != selectedValue) {
categoriesItems.add(
DropdownMenuItem(
child: Text(snapshotAsMap[i]['category']),
value: snapshotAsMap[i]['category'],
),
);
}
}
return Padding(
padding: const EdgeInsets.only(left: 18.0, right: 18, top: 10),
child: Container(
padding: EdgeInsets.only(left: 25, right: 25),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1),
borderRadius: BorderRadius.circular(25)),
child: DropdownButton<String>(
items: categoriesItems,
icon: const Icon(
Icons.expand_more,
color: Colors.grey,
),
iconSize: 24,
elevation: 16,
isExpanded: true,
style: const TextStyle(color: Colors.grey),
underline: SizedBox(),
onChanged: (value) {
setState(() {
widget.selectedValue = value;
});
},
value: selectedValue,
hint: Text('My courses'),
),
),
);
}
})};
2.Usage
you can called it like this
String selectedValue="Select Here"
rangeLists(selectedValue)//call this as a widget in ur ui
It will handle all list from the Api backend when u return a list u don't need to worry about the error any more
child: DropdownButtonFormField<String>(
hint: Text(widget.hintText == "Select value..."
? "Select ${widget.caption}"
: widget.hintText),
items: getItems(),
value: **checkValue(widget.currentValue)**,
iconSize: 30,
onChanged: widget.onChanged,
),
String? **checkValue(String? value)** {
var arrRet = widget.items.where(
(item) => item[widget.valueMember].toString() == value.toString());
if (arrRet.isEmpty && widget.items.isNotEmpty)
return widget.items[0][widget.valueMember].toString();
return value;
}