Home > Mobile >  Why the DropDownButtonFormField does not need a setState to to rebuild, while the DropDownButton nee
Why the DropDownButtonFormField does not need a setState to to rebuild, while the DropDownButton nee

Time:09-27

I tried to figure it out, and read the documentation for both but I didn't find an answer, here is an example of what I mean:

List<String> items = ["item1", "item2", "item3", "item4"];
class HomeScreen extends StatelessWidget {
  HomeScreen({super.key});
  String selectedItem = items[0];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: DropdownButton(
              value: selectedItem,
              onChanged: (value) => selectedItem = value!,
              items: items
                  .map(
                    (e) => DropdownMenuItem<String>(
                      value: e,
                      child: Text(e),
                    ),
                  )
                  .toList(),
            ),
...

that's just a simple stateless widget with a DropdownButton at the center: output of the code above

if we just change the widget to a DropdownButtonFormField with all else remains the same, changes to the selected item reflect in the UI: output of the same code after changing the widget to a DropdownButtonFormField

CodePudding user response:

If you dig inside DropdownButtonFormField you will see it keeps a separate value for the menu inside its state. If you explore the code it says

onChanged: onChanged == null ? null : state.didChange,

state.didChange looks like:

  @override
  void didChange(T? value) {
    super.didChange(value);
    final DropdownButtonFormField<T> dropdownButtonFormField = widget as DropdownButtonFormField<T>;
    assert(dropdownButtonFormField.onChanged != null);
    dropdownButtonFormField.onChanged!(value);
  }

and super.didChange looks like

  void didChange(T? value) {
    setState(() {
      _value = value;
      _hasInteractedByUser.value = true;
    });
    Form.of(context)?._fieldDidChange();
  }

This changes the iternal value of the state and calls setState so that it refreshes the UI for it.

As a result, even if you change this line of your code:

onChanged: (value) => selectedItem = value!,

to

onChanged: (value){},

It still works visually, because it doesn't actually use selectedItem but the internal value.

  • Related