I have a fairly complex form where the drop down list changes based on previous selections. For example, if I select option Delivery or Pickup, the next drop down would be based on that selection and show either a store list or delivery options.
I have the below code and have a tried a few options, but the drop down doesn't seem to update based on selection, however I can't figure out why as it should be refreshed with the state change.
Any suggestion on the best approach for this? I thought it might be related to the Key needing to be unique but that doesn't seem to solve the problem and also causes other issues like clear of selected item when other fields change.
Question: How can you provide dynamic drop downs based on previous form field selection in Dart/Flutter?
DropDownInputField(
inputList: const [
'Delivery',
'Pickup',
],
onchanged: (selection) {
setState(() {
order.shippingOption = selection;
});
},
name: 'Shipping Option',
),
const SizedBox(
height: 20,
),
DropDownInputField(
inputList: retrieveList(order.shippingOption),
onchanged: (value) => order.deliveryOption = value,
name: 'Delivery Options',
),
Option generation Function
List<String> retrieveList(String shippingOption) {
switch (shippingOption.toLowerCase()) {
case "delivery":
return [
'Standard',
'Express',
];
break;
case "pickup":
return [
'Store 1',
'Store 2',
];
break;
State Class
class _ShippingFormScreenState extends State<ShippingFormScreen>
with SingleTickerProviderStateMixin {
TabController tabController;
Order order;
CodePudding user response:
generation Function
will decide the second dropdown items. But If you click to select the second drop down item 1st, it will through errors. To handle this situation, you need to update the second dropdown value as well. You can set the second dropdown value=null. Therefor, we need to use nullable String for selection value.
On First DropDownFiled onChanged
make seceond dropdown value null.
DropDownInputField(
inputList: const [
'Delivery',
'Pickup',
],
onchanged: (selection) {
setState(() {
order.shippingOption = selection;
order.deliveryOption = null;
});
},
name: 'Shipping Option',
),
And second dropdown seems ok . But make sure to make those field as nullable. I will encourage you to check this
CodePudding user response:
You have to disable the bottom drop down based on top drop down using onChanged
From the documentation, Disabling like this:
If items or onChanged is null, the button will be disabled, the down arrow will be grayed out, and the disabledHint will be shown (if provided)
So we will disable bottom drop down and see if order changes. You should change bottom drop down onChanged function to this
items: retrieveList(order.shippingOption),
onChanged: order.shippingOption == null
? null
: (value) {
setState(() {
order.deliveryOption = value!;
});
},
and change retrieveList
to this:
List<String> retrieveList(String? shippingOption) {
if (shippingOption == null) return [];
switch (shippingOption.toLowerCase()) {
case "delivery":
return [
'Standard',
'Express',
];
case "pickup":
return [
'Store 1',
'Store 2',
];
default:
throw Exception('Unknown shipping option');
}
}
}
Full code of the widget
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Order order = Order();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropDownInputField(
items: const [
'Delivery',
'Pickup',
],
onChanged: (value) {
setState(() {
order.shippingOption = value!;
});
},
value: order.shippingOption,
name: 'Shipping Option',
),
const SizedBox(
height: 20,
),
DropDownInputField(
items: retrieveList(order.shippingOption),
onChanged: order.shippingOption == null
? null
: (value) {
setState(() {
order.deliveryOption = value!;
});
},
name: 'Delivery Options',
value: order.deliveryOption,
),
],
),
),
);
}
List<String> retrieveList(String? shippingOption) {
if (shippingOption == null) return [];
switch (shippingOption.toLowerCase()) {
case "delivery":
return [
'Standard',
'Express',
];
case "pickup":
return [
'Store 1',
'Store 2',
];
default:
throw Exception('Unknown shipping option');
}
}
}
class Order {
String? shippingOption;
String? deliveryOption;
}
class DropDownInputField extends StatelessWidget {
const DropDownInputField({
Key? key,
required this.items,
required this.onChanged,
required this.value,
required this.name,
}) : super(key: key);
final List<String> items;
final ValueChanged<String?>? onChanged;
final String? value;
final String name;
@override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: value,
hint: Text(name),
items: <DropdownMenuItem<String>>[
...items.map((e) => DropdownMenuItem(
child: Text(e),
value: e,
))
],
onChanged: onChanged,
);
}
}