Reputation: 85
I'm implementing corresponding dropdowns (where the options of the second dropdown depend on the first like so) that uses a list of objects in this format:
List<State> states = [
new State(stateId: 1, stateName: "New York", cities: [
new City(cityId: 1, cityName: "New York City"),
new City(cityId: 2, cityName: "Buffalo") ...
And the widget code is like so:
children: <Widget>[
DropdownButtonFormField<int>(
decoration: InputDecoration(labelText: 'State'),
value: selectedStateId,
items: states.map((State state) {
return new DropdownMenuItem<int>(
value: state.stateId,
child: new Text(states.singleWhere((x) => x.stateId == state.stateId).stateName),
);
}).toList(),
onChanged: (int newStateId) {
setState(() {
this.selectedCityId = states.singleWhere((x) => x.stateId == newStateId).cities[0].cityId; // set to first city for this state
this.selectedStateId = = newStateId;
});
},
),
DropdownButtonFormField<int>(
decoration: InputDecoration(labelText: 'City'),
value: selectedCityId,
items: states.singleWhere((x) => x.stateId == selectedStateId)
.cities
.map((City city) {
return new DropdownMenuItem<int>(
value: city.cityId,
child: new Text(states
.singleWhere((x) => x.stateId == selectedStateId).cities.singleWhere((x) => x.cityId == city.cityId).cityName),
);
}).toList(),
onChanged: (int newCityId) {
setState(() {
this.selectedCityId = newCityId;
});
},
)
],
When I change the State dropdown in this example, I get an error: "There should be exactly one item with [DropdownButton]'s value: 1. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value".
The "1" in the above error corresponds to whatever the selected city value was prior to changing the state, so I know that the error relates to the fact that it's still looking for the old selectedCityId and it is no longer in the item list, but I'm not sure why as I've changed that value in setState. A key to this issue, I believe, is the same exact code works if I just change the DropDownButtonFormField to regular DropDownButtons, but I'd like to use the built in label text that comes along with the former.
Upvotes: 2
Views: 6023
Reputation: 54367
Edit
You can use key: UniqueKey()
int City DropdownButtonFormField
DropdownButtonFormField<int>(
key: UniqueKey(),
decoration: InputDecoration(labelText: 'City'),
You can copy paste run full code below
You can set selectedStateId
and selectedCityId
to states
's attribute
You can not directly set to a value like selectedStateId = 1
, because system will compare address
code snippet
int selectedStateId;
int selectedCityId;
@override
void initState() {
selectedStateId = states[0].stateId;
selectedCityId = states[0].cities[0].cityId;
super.initState();
}
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class City {
int cityId;
String cityName;
City({this.cityId, this.cityName});
}
class CountryState {
int stateId;
String stateName;
List<City> cities;
CountryState({this.stateId, this.stateName, this.cities});
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
List<CountryState> states = [
CountryState(stateId: 1, stateName: " York", cities: [
City(cityId: 1, cityName: "York City"),
City(cityId: 2, cityName: "Buffalo")
]),
CountryState(stateId: 2, stateName: "A", cities: [
City(cityId: 3, cityName: "A1"),
City(cityId: 4, cityName: "A2")
])
];
int selectedStateId;
int selectedCityId;
@override
void initState() {
selectedStateId = states[0].stateId;
selectedCityId = states[0].cities[0].cityId;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButtonFormField<int>(
decoration: InputDecoration(labelText: 'State'),
value: selectedStateId,
items: states.map((CountryState state) {
return DropdownMenuItem<int>(
value: state.stateId,
child: Text(states
.singleWhere((x) => x.stateId == state.stateId)
.stateName),
);
}).toList(),
onChanged: (int StateId) {
setState(() {
this.selectedCityId = states
.singleWhere((x) => x.stateId == StateId)
.cities[0]
.cityId; // set to first city for this state
this.selectedStateId = StateId;
});
},
),
DropdownButtonFormField<int>(
key: UniqueKey(),
decoration: InputDecoration(labelText: 'City'),
value: selectedCityId,
items: states
.singleWhere((x) => x.stateId == selectedStateId)
.cities
.map((City city) {
return DropdownMenuItem<int>(
value: city.cityId,
child: Text(states
.singleWhere((x) => x.stateId == selectedStateId)
.cities
.singleWhere((x) => x.cityId == city.cityId)
.cityName),
);
}).toList(),
onChanged: (int CityId) {
setState(() {
this.selectedCityId = CityId;
});
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Upvotes: 4
Reputation: 85
I posted this example on the Flutter issues board and they could not duplicate on the latest stable version (1.20.2 as of this comment). I upgraded my flutter version (I was on 1.17.1) and the same code ran without issue, so for anyone experiencing the same issue I suggest updating your flutter version.
Upvotes: 0