15

I' m gettings items from JSON and displaying them in a dropdown. When the person select an item from the dropdown list, I get the selection but the selected item doesn't change.

For example we have (tokyo, paris, new york) in the list. By default selection is tokyo. When the person select paris, I get it but the selection doesn't change in the dropdown.

Here is my code:

new DropdownButton(
  value: cities.elementAt(0),
  hint: new Text("Ville"),
  items: cities.map((String value) {
    return new DropdownMenuItem(
      value: value,
      child: new Row(
        children: <Widget>[
          new Icon(
            Icons.location_city,
            color: Colors.deepOrange,
          ),
          new Text(value)
        ],
      ),
    );
  }).toList(),
  onChanged: (String value) {
    getTravelCity(value);
  },
),

When the person select an item, it still showing the default value.

Matt Ke
  • 3,599
  • 12
  • 30
  • 49
Pondikpa Tchabao
  • 3,285
  • 6
  • 17
  • 24

10 Answers10

26

Make sure you are not declaring the selectedValue inside the Widget the below example works for me perfectly.

enter image description here

here is the working code on dartpad to test it out

var currentSelectedValue;
const deviceTypes = ["Mac", "Windows", "Mobile"];

 Widget typeFieldWidget() {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 20),
      child: FormField<String>(
        builder: (FormFieldState<String> state) {
          return InputDecorator(
            decoration: InputDecoration(
                border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(5.0))),
            child: DropdownButtonHideUnderline(
              child: DropdownButton<String>(
                hint: Text("Select Device"),
                value: currentSelectedValue,
                isDense: true,
                onChanged: (newValue) {
                  setState(() {
                    currentSelectedValue = newValue;
                  });
                  print(currentSelectedValue);
                },
                items: deviceTypes.map((String value) {
                  return DropdownMenuItem<String>(
                    value: value,
                    child: Text(value),
                  );
                }).toList(),
              ),
            ),
          );
        },
      ),
    );
  }

Alternate Solution

In case you are using DropDownButton in a stateless widget, You can rebuild dropDown Widget by wrapping it in ValueListenableBuilder.

class MyWidget extends StatelessWidget {
  String? currentSelectedValue;

   final ValueNotifier<List<String>> _listNotifier =
      ValueNotifier<List<String>>(["Mac", "Windows", "Mobile"]);

  Widget typeFieldWidget() {
    return ValueListenableBuilder(
      valueListenable: _listNotifier,
      builder: (BuildContext context, List<String> list, Widget? child) {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: FormField<String>(
            builder: (FormFieldState<String> state) {
              return InputDecorator(
                decoration: InputDecoration(
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(5.0))),
                child: DropdownButtonHideUnderline(
                  child: DropdownButton<String>(
                    hint: Text("Select Device"),
                    value: currentSelectedValue,
                    isDense: true,
                    onChanged: (newValue) {
                        currentSelectedValue = newValue;
                      _listNotifier.notifyListeners();
                    },
                    items: list.map((String value) {
                      return DropdownMenuItem<String>(
                        value: value,
                        child: Text(value),
                      );
                    }).toList(),
                  ),
                ),
              );
            },
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: typeFieldWidget(),
    ));
  }
}

Mahesh Jamdade
  • 17,235
  • 8
  • 110
  • 131
  • that works if you use `SetState` for my case I need a `StatelessWidget` – Dani Dec 03 '19 at 08:30
  • 2
    thats not possible to render new content on screen without doing a setState thats how flutter works,it needs to redraw the widgets by doing setState,you may change the variable values in memory but to present with updated value on the screen you need to redraw the ui by calling setState; – Mahesh Jamdade Dec 03 '19 at 08:57
  • so basically, if I also need to add a new element on my `ListView` I'd also have to do the same: move the `StatelessWidget` to `Stateful` and change the state only for that page – Dani Dec 03 '19 at 16:00
  • 1
    if you tried the above code it will work,if you want to add new element simply add that element to your list e.g ```deviceTypes``` in above code,so that the new element gets reflected in the drop down,whenever the drop down list is rendered on screen it fetches the newlist. – Mahesh Jamdade Feb 06 '20 at 00:00
  • 1
    I have updated the answer with the dartpad link of the working code please check it – Mahesh Jamdade Feb 06 '20 at 00:18
  • I came again after a while to the same error. This time I was using a `ShowDialog` and for this you need to use `StatefulBuilder` despite you already have a `Stateful` widget: https://github.com/flutter/flutter/issues/19343 – Dani Feb 18 '20 at 10:34
  • 1
    Most important gotcha: "Make sure you are not declaring the selectedValue inside the Widget"! – Giddy Naya Nov 06 '21 at 16:08
  • @Dani I have updated the answer, which allows you to use the DropdownButton in a stateless widget. – Mahesh Jamdade Oct 04 '22 at 04:09
11
This is the solution: "StatefulBuilder()"

       Future<void> AgregarContacto() async {
             String dropdownValue = 'Exento';
             return showDialog<void>(
           context: context,
           barrierDismissible: true, // user must tap button!
           builder: (BuildContext context) {
                 return StatefulBuilder(
                   builder: (BuildContext context, StateSetter setState) {
                   return AlertDialog(
                   title: Text('Agregar cliente', textAlign: TextAlign.center,),
                   content:
                     SingleChildScrollView(
                       child: ListBody(
                         children: <Widget>[
                           Center(
                             child: Material(
                               type: MaterialType.transparency,
                               child: DropdownButton<String>(
                                 items: <String>[
                                   'Responsable inscripto',
                                   'Monotributista',
                                   'Exento',
                                   'Consumidor final',
                                   'No responsable'
                                 ]
                                     .map<DropdownMenuItem<String>>((
                                     String value) {
                                   return DropdownMenuItem<String>(
                                     value: value,
                                     child: Text(value),
                                   ); //DropMenuItem
                                 }).toList(),
                                 value: dropdownValue,
                                 onChanged: (String newValue) {
                                   setState(() {
                                     dropdownValue = newValue;
                                     print("new${newValue}");
                                   }); //setState
                                 },
                                 //OnChange
                                 isExpanded: false,
                                 hint: Text('Responsable inscripto',
                                   style: TextStyle(color: Colors.black),),
                               ),
                             ), //Material
                           ), //Center
                         ],
                       ),
                     ),
                   actions: <Widget>[
                         FlatButton(
                           child: Text('Regret'),
                           onPressed: () {
                             Navigator.of(context).pop();
                          },
                       ),
                    ],
                 );
            }
            );
           },
         );
       }
Ezequiel
  • 111
  • 1
  • 2
  • This is exactly what I was looking for. I have a very long stateless screen and wanted to have only one dropdown in that stateful. This post just gave me that. cheers. I tested it and it works like charm. – spatik Sep 19 '20 at 17:20
3

this was a bug reported in the Github of Flutter.

The correct implementions is:

class _DropdownButtonBugState extends State<DropdownButtonBug> {

  final List<String> _items = ['One', 'Two', 'Three', 'Four'].toList();

  String _selection;

  @override
  void initState() {
    _selection = _items.first;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final dropdownMenuOptions = _items
      .map((String item) =>
        new DropdownMenuItem<String>(value: item, child: new Text(item))
      )
      .toList();

    return new Scaffold(
      body: new DropdownButton<String>(
        value: _selection,
        items: dropdownMenuOptions,
        onChanged: (s) {
          setState(() {
            _selection = s;
          });
        }
      )
    );
  }
}
2

The Dropdown Button can be wrapped inside a StatefulBuilder() widget if the selected values are not updated after the selction.

HRISHIKESH DESHMUKH
  • 283
  • 1
  • 2
  • 10
0

Because you need to update the "value: " parameter with "setState(() {});" method and the new value must be of the same type of the one passed to "onChanged:". I suggest tho do this because your are able to change dynamically your icon.

1) declare a new class in the top of the file;

class city {
  final String name;
  final IconData icon;

  const city({
    this.name,
    this.icon,
  });
}

2) the add this in the state of your widget:

class yourState extend<myWidget>{

List<city> cities = [
    new city(name: "tokio", icon: Icons.location_city),
    new city(name: "paris", icon: Icons.location_city),
    new city(name: "new york", icon: Icons.location_city),
  ];

int index = 0;

Widget drop() {
    return DropdownButton(
        value: cities[index],
        hint: new Text("Ville"),
        items: cities.map((city value) {
          return new DropdownMenuItem(
            value: value,
            child: new Row(
              children: <Widget>[
                new Icon(
                  value.icon,
                  color: Colors.deepOrange,
                ),
                new Text(value.name)
              ],
            ),
          );
        }).toList(),
        onChanged: (city value) {
          setState(() {
            index = cities.indexOf(value);
            print(index);
          });
          //getTravelCity(value.name);
        });
  }

}

now call "drop()" where you want your DropDown. Bye!!

Raoul Scalise
  • 486
  • 4
  • 7
  • The `value` property of `DropdownButton`has been updated I think. Now rather than assigning the actual `String Value`, you can just use index instead – Bing Gan Jan 15 '19 at 20:31
0

Is used this code in my most recent app and it works. Maybe you could compare it to your code.

return DropdownButton<String>(
    style: Theme.of(context).textTheme.title,
    items: _persons.map((String val) {
      return new DropdownMenuItem<String>(
        value: val,
        child: new Container(
          child: new Card(
            child: new Row(children: <Widget>[
              new Icon(Icons.person),
              new Text(val),
            ]),
          ),
        ),
      );
    }).toList(),
    hint: new Card(
        child: new Row(children: <Widget>[
      new Icon(Icons.person),
      new Text("check"),
    ])),
    value: _selectedPerson,
    onChanged: (newVal) {
      _selectedPerson = newVal;
      setState(() {});
    });
Jan D.M.
  • 2,284
  • 2
  • 20
  • 32
0

You hard-coded the first city as value for the dropdown.

new DropdownButton(
            value: cities.elementAt(0), //this one
...

You need to have some state variable to hold the selected city.

class YourStatefulWidgetState extends State<YourStatefulWidget> {
  @override
  initState() {
   super.initState();
   selectedCity = cities.elementAt(0)
  }

 @override
 Widget build(BuildContext context) {
   ...
   new DropdownButton(
            value: selectedCity,
            hint: new Text("Ville"),
            items: cities.map((String value) {
              return new DropdownMenuItem(value: value,
                child: new Row(
                  children: <Widget>[
                    new Icon(
                      Icons.location_city, color: Colors.deepOrange,),
                    new Text(value)
                  ],
                ),);
            }).toList(),
            onChanged: (String value) {
              setState(() {
                selectedCity = getTravelCity(value);
              });
            },),
     ...
Dinesh Balasubramanian
  • 20,532
  • 7
  • 64
  • 57
  • i forget to tell you that the dropdown is in an AlertDialog – Pondikpa Tchabao Sep 03 '18 at 17:13
  • @PondikpaTchabao Do you have your own dialog widget? I think this code does not work in your case, because `setState` call is updating the widget that built the dialog, not the dialog. You should create your own 'DropdownAlertDialog' `Stateful` widget – Kirill Shashov Sep 04 '18 at 11:14
0

For those who are facing this problem in future. Use variable to set the value to DropdownButton and when the onChanged called update the variable using setState

Expanded(child: DropdownButton<String>(
          items: cities.map((String value) {
            return DropdownMenuItem<String>(
              value: value,
              child: Text(value),
            );
          }).toList(),
          value: city,
          onChanged: (String val) {
            _onDropDownChanged(val);
          },
        ),)

update the city variable by using state

void _onDropDownChanged(String val) {
    setState(() {
      this.city = val;
    });
  }

For further information check the link below

https://github.com/FazalHussain/Flutter-Demo/blob/trip_cost_app/lib/main.dart

Fazal Hussain
  • 1,129
  • 12
  • 28
0

I tired all the options, including all the flutter issues. But after 2 hours of search. I found my solution for my Bloc implemented widget.

 onChanged: (Object value) {
     selectedCity = getTravelCity(value);
     setState((){}; });
        );

You can feel the difference here. Since I'm populating the list under BlocBuilder with some state, It won't allow you to rebuild the dropdown menu. So after setting the dropdownValue then call the setState method. It will allow you to reflect the value.

Thanks!

EngineSense
  • 3,266
  • 8
  • 28
  • 44
0

make a variable as the default value of your dropdownmenu value, then in your onChanged, variable = value; and do a setstate.