How to implement drop down list in flutter?
Drop Down-MenuDartFlutterDrop Down-Menu Problem Overview
I have a list of locations that i want to implement as a dropdown list in Flutter. Im pretty new to the language. Here's what i have done.
new DropdownButton(
value: _selectedLocation,
onChanged: (String newValue) {
setState(() {
_selectedLocation = newValue;
});
},
items: _locations.map((String location) {
return new DropdownMenuItem<String>(
child: new Text(location),
);
}).toList(),
This is my list of items:
List<String> _locations = ['A', 'B', 'C', 'D'];
And I am getting the following error.
Another exception was thrown: 'package:flutter/src/material/dropdown.dart': Failed assertion: line 468 pos 15: 'value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1': is not true.
I assume the value of _selectedLocation
is getting null. But i am initialising it like so.
String _selectedLocation = 'Please choose a location';
Drop Down-Menu Solutions
Solution 1 - Drop Down-Menu
Try this
DropdownButton<String>(
items: <String>['A', 'B', 'C', 'D'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
)
Solution 2 - Drop Down-Menu
For the solution, scroll to the end of the answer.
First of all, let's investigate what the error says (I have cited the error that's thrown with Flutter 1.2, but the idea is the same):
> Failed assertion: line 560 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem
There are four or
conditions. At least one of them must be fulfilled:
- Items (a list of
DropdownMenuItem
widgets) were provided. This eliminatesitems == null
. - Non-empty list was provided. This eliminates
items.isEmpty
. - A value (
_selectedLocation
) was also given. This eliminatesvalue == null
. Note that this isDropdownButton
's value, notDropdownMenuItem
's value.
Hence only the last check is left. It boils down to something like:
> Iterate through DropdownMenuItem
's. Find all that have a value
that's equal to _selectedLocation
. Then, check how many items matching it were found. There must be exactly one widget that has this value. Otherwise, throw an error.
The way code is presented, there is not a DropdownMenuItem
widget that has a value of _selectedLocation
. Instead, all the widgets have their value set to null
. Since null != _selectedLocation
, last condition fails. Verify this by setting _selectedLocation
to null
- the app should run.
To fix the issue, we first need to set a value on each DropdownMenuItem
(so that something could be passed to onChanged
callback):
return DropdownMenuItem(
child: new Text(location),
value: location,
);
The app will still fail. This is because your list still does not contain _selectedLocation
's value. You can make the app work in two ways:
- Option 1. Add another widget that has the value (to satisfy
items.where((DropdownMenuItem<T> item) => item.value == value).length == 1
). Might be useful if you want to let the user re-selectPlease choose a location
option. - Option 2. Pass something to
hint:
paremter and setselectedLocation
tonull
(to satisfyvalue == null
condition). Useful if you don't wantPlease choose a location
to remain an option.
See the code below that shows how to do it:
import 'package:flutter/material.dart';
void main() {
runApp(Example());
}
class Example extends StatefulWidget {
@override
State<StatefulWidget> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// List<String> _locations = ['Please choose a location', 'A', 'B', 'C', 'D']; // Option 1
// String _selectedLocation = 'Please choose a location'; // Option 1
List<String> _locations = ['A', 'B', 'C', 'D']; // Option 2
String _selectedLocation; // Option 2
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: DropdownButton(
hint: Text('Please choose a location'), // Not necessary for Option 1
value: _selectedLocation,
onChanged: (newValue) {
setState(() {
_selectedLocation = newValue;
});
},
items: _locations.map((location) {
return DropdownMenuItem(
child: new Text(location),
value: location,
);
}).toList(),
),
),
),
);
}
}
Solution 3 - Drop Down-Menu
Use StatefulWidget
and setState
to update dropdown.
String _dropDownValue;
@override
Widget build(BuildContext context) {
return DropdownButton(
hint: _dropDownValue == null
? Text('Dropdown')
: Text(
_dropDownValue,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: ['One', 'Two', 'Three'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
_dropDownValue = val;
},
);
},
);
}
initial state of dropdown:
Open dropdown and select value:
Reflect selected value to dropdown:
Solution 4 - Drop Down-Menu
If you don't want the Drop list
to show up like a popup. You can customize it this way just like me (it will show up as if on the same flat, see image below):
After expand:
Please follow the steps below:
First, create a dart file named drop_list_model.dart
:
import 'package:flutter/material.dart';
class DropListModel {
DropListModel(this.listOptionItems);
final List<OptionItem> listOptionItems;
}
class OptionItem {
final String id;
final String title;
OptionItem({@required this.id, @required this.title});
}
Next, create file file select_drop_list.dart
:
import 'package:flutter/material.dart';
import 'package:time_keeping/model/drop_list_model.dart';
import 'package:time_keeping/widgets/src/core_internal.dart';
class SelectDropList extends StatefulWidget {
final OptionItem itemSelected;
final DropListModel dropListModel;
final Function(OptionItem optionItem) onOptionSelected;
SelectDropList(this.itemSelected, this.dropListModel, this.onOptionSelected);
@override
_SelectDropListState createState() => _SelectDropListState(itemSelected, dropListModel);
}
class _SelectDropListState extends State<SelectDropList> with SingleTickerProviderStateMixin {
OptionItem optionItemSelected;
final DropListModel dropListModel;
AnimationController expandController;
Animation<double> animation;
bool isShow = false;
_SelectDropListState(this.optionItemSelected, this.dropListModel);
@override
void initState() {
super.initState();
expandController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 350)
);
animation = CurvedAnimation(
parent: expandController,
curve: Curves.fastOutSlowIn,
);
_runExpandCheck();
}
void _runExpandCheck() {
if(isShow) {
expandController.forward();
} else {
expandController.reverse();
}
}
@override
void dispose() {
expandController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 17),
decoration: new BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.white,
boxShadow: [
BoxShadow(
blurRadius: 10,
color: Colors.black26,
offset: Offset(0, 2))
],
),
child: new Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.card_travel, color: Color(0xFF307DF1),),
SizedBox(width: 10,),
Expanded(
child: GestureDetector(
onTap: () {
this.isShow = !this.isShow;
_runExpandCheck();
setState(() {
});
},
child: Text(optionItemSelected.title, style: TextStyle(
color: Color(0xFF307DF1),
fontSize: 16),),
)
),
Align(
alignment: Alignment(1, 0),
child: Icon(
isShow ? Icons.arrow_drop_down : Icons.arrow_right,
color: Color(0xFF307DF1),
size: 15,
),
),
],
),
),
SizeTransition(
axisAlignment: 1.0,
sizeFactor: animation,
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.only(bottom: 10),
decoration: new BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
color: Colors.white,
boxShadow: [
BoxShadow(
blurRadius: 4,
color: Colors.black26,
offset: Offset(0, 4))
],
),
child: _buildDropListOptions(dropListModel.listOptionItems, context)
)
),
// Divider(color: Colors.grey.shade300, height: 1,)
],
),
);
}
Column _buildDropListOptions(List<OptionItem> items, BuildContext context) {
return Column(
children: items.map((item) => _buildSubMenu(item, context)).toList(),
);
}
Widget _buildSubMenu(OptionItem item, BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 26.0, top: 5, bottom: 5),
child: GestureDetector(
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
padding: const EdgeInsets.only(top: 20),
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey[200], width: 1)),
),
child: Text(item.title,
style: TextStyle(
color: Color(0xFF307DF1),
fontWeight: FontWeight.w400,
fontSize: 14),
maxLines: 3,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis),
),
),
],
),
onTap: () {
this.optionItemSelected = item;
isShow = false;
expandController.reverse();
widget.onOptionSelected(item);
},
),
);
}
}
Initialize value:
DropListModel dropListModel = DropListModel([OptionItem(id: "1", title: "Option 1"), OptionItem(id: "2", title: "Option 2")]);
OptionItem optionItemSelected = OptionItem(id: null, title: "Chọn quyền truy cập");
Finally use it:
SelectDropList(
this.optionItemSelected,
this.dropListModel,
(optionItem){
optionItemSelected = optionItem;
setState(() {
});
},
)
Solution 5 - Drop Down-Menu
you have to take this into account (from DropdownButton docs):
> "The items must have distinct values and if value isn't null it must > be among them."
So basically you have this list of strings
List<String> _locations = ['A', 'B', 'C', 'D'];
And your value in Dropdown value property is initialised like this:
String _selectedLocation = 'Please choose a location';
Just try with this list:
List<String> _locations = ['Please choose a location', 'A', 'B', 'C', 'D'];
That should work :)
Also check out the "hint" property if you don't want to add a String like that (out of the list context), you could go with something like this:
DropdownButton<int>(
items: locations.map((String val) {
return new DropdownMenuItem<String>(
value: val,
child: new Text(val),
);
}).toList(),
hint: Text("Please choose a location"),
onChanged: (newVal) {
_selectedLocation = newVal;
this.setState(() {});
});
Solution 6 - Drop Down-Menu
For anyone interested to implement a DropDown
of custom class
you can follow the bellow steps.
-
Suppose you have a class called
Language
with the following code and astatic
method which returns aList<Language>
class Language { final int id; final String name; final String languageCode; const Language(this.id, this.name, this.languageCode); } const List<Language> getLanguages = <Language>[ Language(1, 'English', 'en'), Language(2, 'فارسی', 'fa'), Language(3, 'پشتو', 'ps'), ];
-
Anywhere you want to implement a
DropDown
you canimport
theLanguage
class first use it as followDropdownButton( underline: SizedBox(), icon: Icon( Icons.language, color: Colors.white, ), items: getLanguages.map((Language lang) { return new DropdownMenuItem<String>( value: lang.languageCode, child: new Text(lang.name), ); }).toList(), onChanged: (val) { print(val); }, )
Solution 7 - Drop Down-Menu
You need to add value: location
in your code to work it. Check this out.
items: _locations.map((String location) {
return new DropdownMenuItem<String>(
child: new Text(location),
value: location,
);
}).toList(),
Solution 8 - Drop Down-Menu
place the value inside the items.then it will work,
new DropdownButton<String>(
items:_dropitems.map((String val){
return DropdownMenuItem<String>(
value: val,
child: new Text(val),
);
}).toList(),
hint:Text(_SelectdType),
onChanged:(String val){
_SelectdType= val;
setState(() {});
})
Solution 9 - Drop Down-Menu
You can use DropDownButton
class in order to create drop down list :
...
...
String dropdownValue = 'One';
...
...
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
...
...
please refer to this [flutter web page][1] [1]: https://docs.flutter.io/flutter/material/DropdownButton-class.html
Solution 10 - Drop Down-Menu
Let say we are creating a drop down list of currency:
List _currency = ["INR", "USD", "SGD", "EUR", "PND"];
List<DropdownMenuItem<String>> _dropDownMenuCurrencyItems;
String _currentCurrency;
List<DropdownMenuItem<String>> getDropDownMenuCurrencyItems() {
List<DropdownMenuItem<String>> items = new List();
for (String currency in _currency) {
items.add(
new DropdownMenuItem(value: currency, child: new Text(currency)));
}
return items;
}
void changedDropDownItem(String selectedCurrency) {
setState(() {
_currentCurrency = selectedCurrency;
});
}
Add below code in body part:
new Row(children: <Widget>[
new Text("Currency: "),
new Container(
padding: new EdgeInsets.all(16.0),
),
new DropdownButton(
value: _currentCurrency,
items: _dropDownMenuCurrencyItems,
onChanged: changedDropDownItem,
)
])
Solution 11 - Drop Down-Menu
Make your custom Widget:
import 'package:flutter/material.dart';
/// Usage:
/// CustomDropdown<String>(
// items: ['A', 'B', 'C'],
// onChanged: (val) => _selectedValue = val,
// center: true,
// ),
/// --> Remember: f.toString() at line 105 is @override String toString() in your class
// @override
// String toString() {
// return name;
// }
class CustomDropdown<T> extends StatefulWidget {
CustomDropdown({
Key key,
@required this.items,
@required this.onChanged,
this.onInit,
this.padding = const EdgeInsets.only(top: 10.0),
this.height = 40,
this.center = false,
this.itemText,
}) : super(key: key);
/// list item
List<T> items;
/// onChanged
void Function(T value) onChanged;
/// onInit
void Function(T value) onInit;
///padding
EdgeInsetsGeometry padding;
/// container height
double height;
/// center
bool center;
String Function(String text) itemText;
@override
_CustomDropdownState<T> createState() => _CustomDropdownState();
}
class _CustomDropdownState<T> extends State<CustomDropdown<T>> {
/// current selected value
T _selectedValue;
@override
void initState() {
super.initState();
_initValue();
}
@override
Widget build(BuildContext context) {
return _buildBody();
}
/// set default selected value when init
_initValue() {
_selectedValue = widget.items[0];
if (widget.onInit != null) widget.onInit(_selectedValue);
}
_buildBody() {
Color borderLine = Color(0xffc0c0c0);
return Padding(
padding: widget.padding,
child: Row(
mainAxisAlignment: (widget.center)
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: <Widget>[
new Container(
height: widget.height,
padding: EdgeInsets.only(left: 10.0),
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
side: BorderSide(
width: 0.8, style: BorderStyle.solid, color: borderLine),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
),
child: new DropdownButtonHideUnderline(
child: new DropdownButton<T>(
value: _selectedValue,
onChanged: (T newValue) {
setState(() {
_selectedValue = newValue;
widget.onChanged(newValue);
});
},
items: widget.items.map((T f) {
return new DropdownMenuItem<T>(
value: f,
child: new Text(
(widget.itemText != null)
? widget.itemText(f.toString())
: f.toString(),
style: new TextStyle(color: Colors.black),
),
);
}).toList(),
),
),
),
],
),
);
}
}
-
After that, simple call:
CustomDropdown<String>( items: ['A', 'B', 'C'], onChanged: (val) => _selectedValue = val, center: true, )
-
Or with your class:
class Student { int id; String name;
A(this.id,this.name);
//remember override @override String toString() { return name; } }
And call:
CustomDropdown<Student>(
items: studentList,
onChanged: (val) => _selectedValue = val,
center: true,
),
Solution 12 - Drop Down-Menu
Change
List<String> _locations = ['A', 'B', 'C', 'D'];
To
List<String> _locations = [_selectedLocation, 'A', 'B', 'C', 'D'];
_selectedLocation needs to be part of your item List;
Solution 13 - Drop Down-Menu
This is the code which I found most useful. It gives you everything you need. (ctrl+c , ctrl+v will work)
List<String> location = ['One', 'Two', 'Three', 'Four'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Fuel Entry')),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Center(
child: Column(
children: [
DropdownButton<String>(
hint: Text('Select a vehicle '),
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: location.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
],
),
),
),
);
Solution 14 - Drop Down-Menu
This one has the hint text that shows up before selection
DropdownButton<String>(
focusColor: Colors.white,
value: _chosenValue,
//elevation: 5,
style: TextStyle(color: Colors.white),
iconEnabledColor: Colors.black,
items:classes.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: TextStyle(color: Colors.black),
),
);
}).toList(),
hint: Text(
"All Classes",
style: TextStyle(
color: Colors.black, fontSize: 14, fontWeight: FontWeight.w500),
),
onChanged: (String value) {
setState(() {
_chosenValue = value;
});
},
);
Solution 15 - Drop Down-Menu
DropdownButton<int>(
value: 6, //selected
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Theme.of(context).accentColor),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (int? newValue) {},
items: <int>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
);
}).toList(),
)
Solution 16 - Drop Down-Menu
I was facing a similar issue with the DropDownButton when i was trying to display a dynamic list of strings in the dropdown. I ended up creating a plugin : flutter_search_panel. Not a dropdown plugin, but you can display the items with the search functionality.
Use the following code for using the widget :
FlutterSearchPanel(
padding: EdgeInsets.all(10.0),
selected: 'a',
title: 'Demo Search Page',
data: ['This', 'is', 'a', 'test', 'array'],
icon: new Icon(Icons.label, color: Colors.black),
color: Colors.white,
textStyle: new TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20.0, decorationStyle: TextDecorationStyle.dotted),
onChanged: (value) {
print(value);
},
),
Solution 17 - Drop Down-Menu
It had happened to me when I replace the default value with a new dynamic value. But, somehow your code may be dependent on that default value. So try keeping a constant with default value stored somewhere to fallback.
const defVal = 'abcd';
String dynVal = defVal;
// dropdown list whose value is dynVal that keeps changing with onchanged
// when rebuilding or setState((){})
dynVal = defVal;
// rebuilding here...
Solution 18 - Drop Down-Menu
The error you are getting is due to ask for a property of a null object. Your item must be null so when asking for its value to be compared you are getting that error. Check that you are getting data or your list is a list of objects and not simple strings.
Solution 19 - Drop Down-Menu
When I ran into this issue of wanting a less generic DropdownStringButton, I just created it:
dropdown_string_button.dart
import 'package:flutter/material.dart';
// Subclass of DropdownButton based on String only values.
// Yes, I know Flutter discourages subclassing, but this seems to be
// a reasonable exception where a commonly used specialization can be
// made more easily usable.
//
// Usage:
// DropdownStringButton(items: ['A', 'B', 'C'], value: 'A', onChanged: (string) {})
//
class DropdownStringButton extends DropdownButton<String> {
DropdownStringButton({
Key key, @required List<String> items, value, hint, disabledHint,
@required onChanged, elevation = 8, style, iconSize = 24.0, isDense = false,
isExpanded = false, }) :
assert(items == null || value == null || items.where((String item) => item == value).length == 1),
super(
key: key,
items: items.map((String item) {
return DropdownMenuItem<String>(child: Text(item), value: item);
}).toList(),
value: value, hint: hint, disabledHint: disabledHint, onChanged: onChanged,
elevation: elevation, style: style, iconSize: iconSize, isDense: isDense,
isExpanded: isExpanded,
);
}
Solution 20 - Drop Down-Menu
Use this code.
class PlayerPreferences extends StatefulWidget {
final int numPlayers;
PlayerPreferences({this.numPlayers});
@override
_PlayerPreferencesState createState() => _PlayerPreferencesState();
}
class _PlayerPreferencesState extends State<PlayerPreferences> {
int dropDownValue = 0;
@override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value: dropDownValue,
onChanged: (int newVal){
setState(() {
dropDownValue = newVal;
});
},
items: [
DropdownMenuItem(
value: 0,
child: Text('Yellow'),
),
DropdownMenuItem(
value: 1,
child: Text('Red'),
),
DropdownMenuItem(
value: 2,
child: Text('Blue'),
),
DropdownMenuItem(
value: 3,
child: Text('Green'),
),
],
),
);
}
}
and in the main body we call as
class ModeSelection extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
child: PlayerPreferences(),
) ,
),
);
}
}
Solution 21 - Drop Down-Menu
it is very simple
DropdownButton<String>(
isExpanded: true,
value: selectedLocation,
icon: const Icon(Icons.arrow_circle_down),
iconSize: 20,
elevation: 16,
underline: Container(),
onChanged: (String newValue) {
setState(() {
selectedLocation = newValue;
});
},
items: List.generate(
_locations.length,
(index) => DropdownMenuItem(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
_locations[index]
),
),
value: _locations[index],
),
),
),
),
Complete Code Example Given Below
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _locations = ['A', 'B', 'C', 'D'];
String selectedLocation = 'A';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: DropdownButton<String>(
isExpanded: true,
value: selectedLocation,
icon: const Icon(Icons.arrow_circle_down),
iconSize: 20,
elevation: 16,
underline: Container(),
onChanged: (String newValue) {
setState(() {
selectedLocation = newValue;
});
},
items: List.generate(
_locations.length,
(index) => DropdownMenuItem(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
_locations[index]
),
),
value: _locations[index],
),
),
),
),
);
}
}