Search code examples
formsflutterdropdown

Changing Two Level DropdownButtonFormField : There should be exactly one item with [DropdownButton]'s value


Despite multiple entries here that seem to have a similar issue i can not get this really working.

I have a Setup of two depended DropdownButtonFormFields where the second changes to another list after the first is changed.

  1. I was able to break down the issue to the persistent remaining of the selected value of the second selection. I expected it to change with my provided value information in the code.

The following error is provided

════════ Exception caught by widgets library ═══════════════════════════════════

There should be exactly one item with [DropdownButton]'s value: GreenBananas. 

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'
The relevant error-causing widget was
    DropdownButtonFormField<String> 
lib/…/testing/test.dart:242
══════════════════════════════════════════════════════════════════

I simplified the example and reconstructed the error to get a better analyse on the issue and get more valuable input from you guys :)

class InputRowTest extends StatefulWidget {
  @override
  _InputRowTestState createState() => _InputRowTestState();
}

class _InputRowTestState extends State<InputRowTest> {
  List<String> list1 = ['Apples', 'Bananas', 'Peaches'];

  List<String> list1_1 = ['GreenApples', 'RedApples', 'YellowApples'];

  List<String> list1_2 = [
    'YellowBananas',
    'BrownBananas',
    'GreenBananas',
    'GreenApples'
  ];

  List<String> list1_3 = [
    'RedPeaches',
    'YellowPeaches',
    'GreenPeaches',
    'GreenApples'
  ];

  List<String> _fromparent;
  int _fromparentint;
  //String selected;

  @override
  void initState() {
    _fromparent = list1_1;
    _fromparentint = 0;
    //selected = list1[0];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    List<List<String>> subLists = [list1_1, list1_2, list1_3];
    _fromparent = subLists[_fromparentint];

    DropdownButtonFormField ddff = DropdownButtonFormField(
      //value: selected, //list1[0],
      //items: list1.map((category) {
      value: _fromparent[0], //Seems this value wont change.
      items: _fromparent.map((category) {
        return DropdownMenuItem(
          value: category,
          child: Container(
            child: Text(category),
          ),
        );
      }).toList(),
      onChanged: (val) => print(val),
    );

    return Center(
      child: Row(
        children: <Widget>[
          Expanded(
            child: DropdownButtonFormField(
              value: list1[0],
              items: list1.map((category) {
                return DropdownMenuItem(
                  value: category,
                  child: Container(
                    child: Text(category),
                  ),
                );
              }).toList(),
              onChanged: (val) {
                setState(() {
                  //selected = val;
                  _fromparentint = list1.indexOf(val);
                });
              },
            ),
          ),
          Expanded(
            child: ddff,
          ),
        ],
      ),
    );
  }
}

Error


Solution

  • I found a workarround on the issue.

    It seems like instead of rebuilding the DropDownFormField, Flutter just considers it as completely ok to keep it. In this case it is also even pretty stubborn.

    As i could not find a way to rebuild the Field, I created a pretty nasty but working. Also still requires some polish.

    I basically let flutter believe I provide a different widget each time.

    class InputRowTest extends StatefulWidget {
      @override
      _InputRowTestState createState() => _InputRowTestState();
    }
    
    class _InputRowTestState extends State<InputRowTest> {
      List<String> list1 = ['Apples', 'Bananas', 'Peaches'];
    
      List<String> list1_1 = ['GreenApples', 'RedApples', 'YellowApples'];
    
      List<String> list1_2 = [
        'YellowBananas',
        'BrownBananas',
        'GreenBananas',
        'GreenApples'
      ];
    
      List<String> list1_3 = [
        'RedPeaches',
        'YellowPeaches',
        'GreenPeaches',
        'GreenApples'
      ];
    
      List<String> _fromparent;
      int _fromparentint;
      Widget ddbff;
      var selected;
      bool chance;
    
      Widget ddff(List<String> list, bool chance) {
        return (chance)
            ? DropdownButtonFormField(
                value: list[0], //Seems this value wont change.
                items: list.map((category) {
                  return DropdownMenuItem(
                    value: category,
                    child: Container(
                      child: Text(category),
                    ),
                  );
                }).toList(),
                onChanged: (val) {
                  print(val);
                },
              )
            : Container(
                child: DropdownButtonFormField(
                  value: list[0], //Seems this value wont change.
                  items: list.map((category) {
                    return DropdownMenuItem(
                      value: category,
                      child: Container(
                        child: Text(category),
                      ),
                    );
                  }).toList(),
                  onChanged: (val) {
                    print(val);
                  },
                ),
              );
      }
    
      @override
      void initState() {
        _fromparent = list1_1;
        _fromparentint = 0;
        selected = list1_1[0];
        chance = true;
        ddbff = ddff(_fromparent, chance);
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        List<List<String>> subLists = [list1_1, list1_2, list1_3];
        _fromparent = subLists[_fromparentint];
    
        chance = !chance;
        ddbff = ddff(_fromparent, chance);
    
        return Center(
          child: Container(
            child: Row(
              children: <Widget>[
                Expanded(
                  child: DropdownButtonFormField(
                    value: list1[0],
                    items: list1.map((category) {
                      return DropdownMenuItem(
                        value: category,
                        child: Container(
                          child: Text(category),
                        ),
                      );
                    }).toList(),
                    onChanged: (val) {
                      setState(() {
                        _fromparentint = list1.indexOf(val);
                      });
                    },
                  ),
                ),
                Expanded(
                  child: ddbff,
                ),
              ],
            ),
          ),
        );
      }
    }
    

    enter image description here