I have a dropdown button which works fine, but when I try to set a default value it will fail with the following error:
'package:flutter/src/material/dropdown.dart': Failed assertion: line 620 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.
This is my dropdown button:
Widget changeWorkspace() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: DropdownButton<AssignedWorkspace>(
isExpanded: true,
hint: Text("SELECT WORKSPACE"),
value: selectedWorkspace,
onChanged: (dropdownValueSelected) {
setState(() {
selectedWorkspace = dropdownValueSelected;
});
},
items: workspaces != null && workspaces.length > 0
? workspaces.map((AssignedWorkspace workspace) {
return new DropdownMenuItem<AssignedWorkspace>(
value: workspace,
child: new Text(workspace.name,
style: new TextStyle(color: Colors.black)),
);
}).toList()
: null),
),
]);
});
}
I've tried to set the value of selectedWorkspace onInit as follows but it fails.
selectedWorkspace = new AssignedWorkspace(
id: userSettings.currentWorkspaceId,
name: userSettings.currentWorkspaceName);
Is there a way of setting a default value in a dropdown button?
import 'package:flutter/material.dart';
import '../config/app_theme.dart';
class DropdownWidget extends StatefulWidget {
final String title;
final List<String> items;
final ValueChanged<String> itemCallBack;
final String currentItem;
final String hintText;
DropdownWidget({
this.title,
this.items,
this.itemCallBack,
this.currentItem,
this.hintText,
});
#override
State<StatefulWidget> createState() => _DropdownState(currentItem);
}
class _DropdownState extends State<DropdownWidget> {
List<DropdownMenuItem<String>> dropDownItems = [];
String currentItem;
AppTheme appTheme;
_DropdownState(this.currentItem);
#override
void initState() {
super.initState();
for (String item in widget.items) {
dropDownItems.add(DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(
fontSize: 16,
),
),
));
}
}
#override
void didUpdateWidget(DropdownWidget oldWidget) {
if (this.currentItem != widget.currentItem) {
setState(() {
this.currentItem = widget.currentItem;
});
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
appTheme = AppTheme(Theme.of(context).brightness);
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 6),
child: Text(
widget.title,
style: appTheme.activityAddPageTextStyle,
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 15),
margin: EdgeInsets.only(top: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0, 2),
blurRadius: 10,
color: Color(0x19000000),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
icon: appTheme.activityAddPageDownArrowSVG,
value: currentItem,
isExpanded: true,
items: dropDownItems,
onChanged: (selectedItem) => setState(() {
currentItem = selectedItem;
widget.itemCallBack(currentItem);
}),
hint: Container(
child: Text(widget.hintText, style: appTheme.hintStyle),
),
),
),
),
],
),
);
}
}
This is my dropDownWidget without optimization. It has currentItem. You could use it like:
DropdownWidget(
title: kStatus,
items: state.customerStepInfo.statusList,
currentItem: status,
hintText: kCommonPick,
itemCallBack: (String status) {
this.status = status;
},
)
You need implement "equals" in class AssignedWorkspace. I used equatable package.
Example class AssignedWorkspace
class AssignedWorkspace extends Equatable {
final String id;
final String name;
AssignedWorkspace(this.id, this.name);
#override
List<Object> get props => [id];
}
For me id of one of the element is null, once added id is made non-null issue got fixed.
I changed the value of the dropdown var to 1 initially
var _value = '1';
So when the dropdown button has to display its value it displays the one whose value I have set 1 as in the items list in DropDownButton
DropdownButton(
underline: Container(),
onChanged: (value) {
setState(() {
_value = value;
});
},
value: _value,
items: [
DropdownMenuItem(
value: "1",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.devices),
SizedBox(width: 10),
Text(
"Consumption",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
DropdownMenuItem(
value: "2",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.solar_panel),
SizedBox(width: 10),
Text(
"Generation",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
],
),
if you want to see only an initial value you can use hint text named parameter of drop down button and set a text widget. i dont know whether it is a good practice or not.
Related
I'm emulating this search and filter github here and the codes are almost the same but the filtered results do not update instantly while I type and also I faced the following issues:
I will have to press enter on my laptop to finally get the filtered list
When I hit the close icon(which is to clear all the words), I will have to tap the searchbar again so that all my listtile are back on the listview.
Here's my code:
class _CurrencySelectState extends State<CurrencySelect> {
late List<Currency> resCur;
String query = '';
#override
void initState() {
super.initState();
resCur = currencyList;
}
void searchCur(String query) {
final List<Currency> filteredCur = currencyList.where((cur) {
final symbolLower = cur.symbol.toLowerCase(); // Search using symbol
final nameLower = cur.country.toLowerCase(); // Search using country
final searchLower = query.toLowerCase();
return symbolLower.contains(searchLower) ||
nameLower.contains(searchLower);
}).toList();
setState(() {
this.query = query;
resCur = filteredCur;
});
}
#override
Widget build(BuildContext context) {
Widget buildCur(Currency cur) => ListTile(
leading: Padding(
padding: EdgeInset.all(5)
child: SizedBox(
child: Column(
children: <Widget>[
SvgPicture.asset(
cur.assetPath,
),
]),
),
),
title: Column(
children: [
Text(
cur.symbol,
style: TextStyle(
...
),
Text(
cur.name,
style: TextStyle(
...
),
],
),
trailing: Text(
"0.25",
style: TextStyle(
...
),
);
return TextButton(
onPressed: () async {
showModalBottomSheet(
enableDrag: false,
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return DraggableScrollableSheet(
expand: false,
builder: (context, scrollController) {
return Column(
children: <Widget>[
SearchWidget(
text: query,
onChanged: searchCur,
hintText: "Enter symbol or country"
),
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: resCur.length,
itemBuilder: (context, int index) {
final cur = resCur[index];
return buildCur(cur);
},
),
)
],
);
},
);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
...
),
SvgPicture.asset(
...
)
],
));
}
}
Searchwidget code:
import 'package:flutter/material.dart';
class SearchWidget extends StatefulWidget {
final String text;
final ValueChanged<String> onChanged;
final String hintText;
const SearchWidget({
Key? key,
required this.text,
required this.onChanged,
required this.hintText,
}) : super(key: key);
#override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final styleActive = TextStyle(color: Colors.black);
final styleHint = TextStyle(color: Colors.black54);
final style = widget.text.isEmpty ? styleHint : styleActive;
return Container(
height: 42,
margin: const EdgeInsets.fromLTRB(16, 16, 16, 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
border: Border.all(color: Colors.black26),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: controller,
decoration: InputDecoration(
icon: Icon(Icons.search, color: style.color),
suffixIcon: widget.text.isNotEmpty
? GestureDetector(
child: Icon(Icons.close, color: style.color),
onTap: () {
controller.clear();
widget.onChanged('');
FocusScope.of(context).requestFocus(FocusNode());
},
)
: null,
hintText: widget.hintText,
hintStyle: style,
border: InputBorder.none,
),
style: style,
onChanged: widget.onChanged,
),
);
}
}
I am using DropdownButton and I am facing the following issue. I'm using a checkbox in elements, but when I click on an element, I don't get a checkmark indicating that the checkbox has been clicked. As a result, I need to close and reopen it, and then I will see the changes that were clicked on the "checkbox". The second problem is that when I select one element, all elements are selected for me. As a final result, I need to get so that I can select an element and the checkbox is immediately marked, if 2 elements are needed, then two, and so on. Tell me how to fix these problems, I will be grateful for the help?
dropdown
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool isChecked = false;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
selectedValue = widget.items[1];
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
items: widget.items
.map((item) => DropdownMenuItem<String>(
value: item,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: Center(
child: Row(
children: [
if (item == selectedValue)
const SizedBox(
width: 0,
),
Expanded(
child: Text(
item,
style: constants.Styles.smallTextStyleWhite,
),
),
Checkbox(
checkColor: Colors.black,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
),
))
.toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
itemHeight: 47,
dropdownMaxHeight: 191,
dropdownWidth: 140,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark,
),
selectedItemBuilder: (context) {
return widget.items.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
items
final List<String> items = const [
"All EV's",
'Main EV',
'<EV2>',
];
I hope this example explains the concept. For simplcity I made simple a new file, run it and see the results:
Then main idea in two lists, _checkList contain values of the CheckBox and _selectedList handles the main dropdown widget to show the selection.
Feel free to ask any questions and I'm happy to help
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AnimationDemo(number: 5);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({Key? key, this.number = 2}) : super(key: key);
final int number;
#override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
late List<bool> _checkList;
late List<int> _selectedIndex;
bool _isOpen = false;
#override
void initState() {
_checkList = List.filled(widget.number, false);
_selectedIndex = <int>[];
super.initState();
}
List<DropDownItem> generateItems() {
var tmp = <DropDownItem>[];
for (var i = 0; i < _checkList.length; i++) {
tmp.add(DropDownItem(
isChecked: _checkList[i],
onChanged: (value) {
setState(() {
_checkList[i] = value!;
if (value && !_selectedIndex.contains(i)) {
_selectedIndex.add(i);
} else {
_selectedIndex.remove(i);
}
});
},
));
}
return tmp;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text((_selectedIndex.isEmpty)
? 'Nothing Selected'
: _selectedIndex.join(',')),
),
GestureDetector(
onTap: () {
setState(() {
_isOpen = !_isOpen;
});
},
child: const Icon(Icons.arrow_downward),
),
],
),
AnimatedOpacity(
opacity: (_isOpen) ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: generateItems(),
),
)
],
),
);
}
}
class DropDownItem extends StatelessWidget {
final bool isChecked;
final Function(bool?)? onChanged;
const DropDownItem({Key? key, this.onChanged, this.isChecked = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(child: Text('Demo item')),
Checkbox(value: isChecked, onChanged: onChanged)
],
);
}
}
Here's how to achieve the Multiselect dropdown with DropdownButton2:
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}
Also, I've added it as an example to the package doc "Example 4" so you can get back to it later.
I'm making a list of notifications using switches (there will be fifteen in total), but the way I did they turn them all on and off together, how do I turn them on and off individually? And do they accept refactoring to make the code cleaner?
I'm using SwitchListTile.
class CardButton extends StatefulWidget {
const CardButton({Key? key}) : super(key: key);
#override
State<CardButton> createState() => _CardButtonState();
}
class _CardButtonState extends State<CardButton> {
bool _toggled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'botton',
style: TextStyle(
color: Colors.black,
),
),
value: _toggled,
onChanged: (bool value) {
setState(() => _toggled = value);
},
),
),
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'botton',
style: TextStyle(
color: Colors.black,
),
),
value: _toggled,
onChanged: (bool value) {
setState(() => _toggled = value);
},
),
),
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'botton',
style: TextStyle(
color: Colors.black,
),
),
value: _toggled,
onChanged: (bool value) {
setState(() => _toggled = value);
},
),
),
],
);
}
}
You need to create variables to hold the switch state for each switch (toggle) - in your case 15 in total.
From your sample code with individual values for each switch:
class CardButton extends StatefulWidget {
const CardButton({Key? key}) : super(key: key);
#override
State<CardButton> createState() => _CardButtonState();
}
class _CardButtonState extends State<CardButton> {
bool _switch1Toggled = false;
bool _switch2Toggled = false;
bool _switch3Toggled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'switch 1',
style: TextStyle(
color: Colors.black,
),
),
value: _switch1Toggled,
onChanged: (bool value) {
setState(() => _switch1Toggled = value);
},
),
),
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'switch 2',
style: TextStyle(
color: Colors.black,
),
),
value: _switch2Toggled,
onChanged: (bool value) {
setState(() => _switch2Toggled = value);
},
),
),
Card(
child: SwitchListTile(
contentPadding: EdgeInsets.only(left: 16.0),
title: Text(
'switch 3',
style: TextStyle(
color: Colors.black,
),
),
value: _switch3Toggled,
onChanged: (bool value) {
setState(() => _switch3Toggled = value);
},
),
),
],
);
}
}
Thank you very much for your tip Ranvir Mohanlal. I created this template based on your information. I think it worked better.
class MultiSwitch extends StatefulWidget {
const MultiSwitch({Key? key}) : super(key: key);
#override
State<MultiSwitch> createState() => _MultiSwitchState();
}
class _MultiSwitchState extends State<MultiSwitch> {
bool val1 = true;
bool val2 = false;
bool val3 = false;
onChangeFunction1(bool newValue1) {
setState(() {
val1 = newValue1;
});
}
onChangeFunction2(bool newValue2) {
setState(() {
val2 = newValue2;
});
}
onChangeFunction3(bool newValue3) {
setState(() {
val3 = newValue3;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
customSwitch('button', val1, onChangeFunction1),
customSwitch('button', val2, onChangeFunction2),
customSwitch('button', val3, onChangeFunction3),
],
),
);
}
}
Widget customSwitch(String text, bool val, Function onChangeMethod) {
return Card(
child: SwitchListTile(
title: Text(
text,
style: const TextStyle(
color: Colors.black,
fontSize: 18,
),
),
value: val,
onChanged: (newValue) {
onChangeMethod(newValue);
}
),
);
}
This code is responsible for editing the user profile. The bottom line is that the user can go to the settings, change the country, city, and these changes will be saved.
But my problem is that the dropdown box (first Pading in code) does not save the value (i.e. the user profile is empty). In the second field (second Pading) I use the controller and the data is successfully updated. Tell me how to make the updated data from the drop-down list saved in the user profile?
class EditAddressFormPage extends StatefulWidget {
const EditAddressFormPage({Key? key}) : super(key: key);
#override
EditPhoneFormPageState createState() {
return EditPhoneFormPageState();
}
}
class EditPhoneFormPageState extends State<EditAddressFormPage> {
final _formKey = GlobalKey<FormState>();
final addressCountryController = TextEditingController();
final addressCityController = TextEditingController();
var user = UserData.myUser;
String? selectedValue;
List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
#override
void dispose() {
addressCountryController.dispose();
addressCityController.dispose();
super.dispose();
}
void updateCountry(String country) {
String formattedPhoneNumber = country.substring(0,country.length);
user.address_country = formattedPhoneNumber;
}
void updateCity(String city) {
String formattedPhoneNumber = city.substring(0, city.length);
user.address_city = formattedPhoneNumber;
}
_goBack(BuildContext context) {
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
const SizedBox(height: 15),
const Align(
alignment: Alignment.center,
child: SizedBox(
width: 270,
child: Text("What is your new address?",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
))),
Padding(
padding: EdgeInsets.only(top: 20),
child: DropdownButtonHideUnderline(
child: DropdownButton2(
hint: Text('Select country', style: TextStyle(
fontSize: 16,
color: Theme.of(context).hintColor,),),
items: items.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(fontSize: 14,),),)).toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;});},
buttonHeight: 40,
buttonWidth: 320,
itemHeight: 40,
),
),),
Padding(
padding: EdgeInsets.only(top: 0),
child: SizedBox(
height: 100,
width: 320,
child: TextFormField(
// Handles Form Validation
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your city';
}
return null;
},
controller: addressCityController,
decoration: const InputDecoration(
labelText: 'City',
),
))),
Padding(
padding: EdgeInsets.only(top: 50),
child: Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: 320,
height: 50,
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate() ) {
updateCountry(addressCountryController.text);
updateCity(addressCityController.text);
Navigator.pop(context);
}
},
style: ElevatedButton.styleFrom(
primary: Colors.black),
child: const Text(
'Update',
style: TextStyle(fontSize: 15),
),
),
)))
]),
));
}
}
Solution
Inside Update Button
Change this
updateCountry(addressCountryController.text);
to
updateCountry(selectedValue);
You are not setting value of addressCountryController.text and trying to update the country name using controller.
Solution
addressCountryController.text = selectedValue;
you can add this line into onChanged function of dropdown button, which will update the selected country name to addressCountryController controller.
What type of data do you want to save ? you want to save the item number and show it on the user profile screen but where? There is a work around though. Use shared_preferences: ^2.0.13 and save the data in the local db so even if your app is terminated you can access the data.
I am trying the code from this video: https://www.youtube.com/watch?v=4AUuhhSakro (or the github: https://github.com/khaliqdadmohmand/flutter_dynamic_dropdownLists/blob/master/lib/main.dart)
The issue is that when we (the viewers of the video) tries to "go back" to change the initial state (not app state, like county or province), the app crashes with this error:
There should be exactly one item with [DropdownButton]'s value: 14.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 827 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
I believe the problem is when the dropdown is built the first time, it can have a null string as the value parameter of the dropdown, but the second time around it crashes on the assert (if you set it to null you crash at value==null and if you don't reset the variable you are using for value then in the new dropdownlist this value is not in the items. (where the count has to be == 1)
This has been racking my brain in my own project too, I thought I had it working, but apparently it's still very much broken.
Flutter : I have error when work with tow dropdown button load one from another
This is a similar problem and that solution has an async in it (but this is simply not working for me).
You can copy paste run full code below
You can in onChanged add _myCity = null;
code snippet
onChanged: (String Value) {
setState(() {
_myState = Value;
_myCity = null;
_getCitiesList();
print(_myState);
});
},
working demo
full code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
_getStateList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dynamic DropDownList REST API'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.topCenter,
margin: EdgeInsets.only(bottom: 100, top: 100),
child: Text(
'KDTechs',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 20),
),
),
//======================================================== State
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myState,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select State'),
onChanged: (String Value) {
setState(() {
_myState = Value;
_myCity = null;
_getCitiesList();
print(_myState);
});
},
items: statesList?.map((item) {
return DropdownMenuItem(
child: Text(item['name']),
value: item['id'].toString(),
);
})?.toList() ??
[],
),
),
),
),
],
),
),
SizedBox(
height: 30,
),
//======================================================== City
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myCity,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select City'),
onChanged: (String Value) {
setState(() {
_myCity = Value;
print(_myCity);
});
},
items: citiesList?.map((item) {
return DropdownMenuItem(
child: Text(item['name']),
value: item['id'].toString(),
);
})?.toList() ??
[],
),
),
),
),
],
),
),
],
),
);
}
//=============================================================================== Api Calling here
//CALLING STATE API HERE
// Get State information by API
List statesList;
String _myState;
String stateInfoUrl = 'http://cleanions.bestweb.my/api/location/get_state';
Future<String> _getStateList() async {
await http.post(stateInfoUrl, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}, body: {
"api_key": '25d55ad283aa400af464c76d713c07ad',
}).then((response) {
var data = json.decode(response.body);
// print(data);
setState(() {
statesList = data['state'];
});
});
}
// Get State information by API
List citiesList;
String _myCity;
String cityInfoUrl =
'http://cleanions.bestweb.my/api/location/get_city_by_state_id';
Future<String> _getCitiesList() async {
await http.post(cityInfoUrl, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}, body: {
"api_key": '25d55ad283aa400af464c76d713c07ad',
"state_id": _myState,
}).then((response) {
var data = json.decode(response.body);
setState(() {
citiesList = data['cities'];
});
});
}
}