Home > front end >  Error in DropdownButton. There should be exactly one item with [DropdownButton]'s value,
Error in DropdownButton. There should be exactly one item with [DropdownButton]'s value,

Time:12-31

I have created the DropdownBotton inside the ListView and i have a list of values which needs to be displayed in the DropdownMenuItem.

I have 2 questions with respect to DropdownBotton,

  1. How to set the default value in the DropdownButton for my scenario.
  2. (console error)
There should be exactly one item with [DropdownButton]'s value: $ 5.50 Pineapple Jack (S). 
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 915 pos 15: 'items == null || items.isEmpty || value == null ||
              items.where((DropdownMenuItem<T> item) {
                return item.value == value;
              }).length == 1'

Below is my code:

ListView.builder(
  scrollDirection: Axis.vertical,
  shrinkWrap: true,
  physics: const ScrollPhysics(),
  itemCount: snapshot.data!.length,
  itemBuilder: (_, index) {

    List<String> selectedItemValue = <String>[
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type1}"),
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type2}"),
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type3}")];
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          margin: const EdgeInsets.symmetric(
              horizontal: 10, vertical: 5),
          padding: const EdgeInsets.all(5.0),
          child: DropdownButtonHideUnderline(
                child: DropdownButton<String>(
                  value: dropdownValue,
                  items: selectedItemValue.map((dropValue) {
                  return DropdownMenuItem<String>(
                              value: dropValue,
                              child: Text(dropValue),
                            );
                          }).toList(),
                          onChanged:
                              (newDropdownValue) {
                            setState(() {
                              dropdownValue =
                              newDropdownValue!;
                              print(
                                  'dropdown: $dropdownValue');
                            });
                          },
                        ),
                      ),
                ),
        ],
    );
  }
),

Please someone help me on this issue, Thanks in advance.

CodePudding user response:

DropdownButton's value should be unique among DropdownMenuItem' values and There must be one DropDownMenuItem in items which exactly matches the current value of the DropDownButton. in your case you are not setting DropdownButtons value from selectedItemValue. Therefore answers for your questions are,

  1. set initial value of the DropdownButton which you want's to be your default value from selectedItemValue.

  2. to avoid the error, move your DropdownButton to separate StatefullWidget (CustomDropdownButton in this case.). change your code as follows.

ListView.builder(
  scrollDirection: Axis.vertical,
  shrinkWrap: true,
  physics: const ScrollPhysics(),
  itemCount: snapshot.data!.length,
  itemBuilder: (_, index) {

    List<String> selectedItemValue = <String>[
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type1}"),
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type2}"),
    ("\$${snapshot.data![index].price} ${snapshot.data![index].productType.type3}")];

final defaultValue = selectedItemValue[1];

        return CustomDropdownButton(defaultValue:defaultValue, values: selectedItemValue, onItemSelected: (value) {print("Selected Item : $value");});
      }
    ),

CustomDropdownButton class

class CustomDropdownMenu extends StatefulWidget {
  const CustomDropdownMenu(
      {Key? key,
      required this.defaultValue,
      required this.values,
      required this.onItemSelected})
      : super(key: key);
  final dynamic Function(String? selectedValue) onItemSelected;
  final String defaultValue;
  final List<String> values;
  @override
  _CustomDropdownMenuState createState() => _CustomDropdownMenuState();
}

class _CustomDropdownMenuState extends State<CustomDropdownMenu> {
  late String dropdownValue;

  @override
  void initState() {
    super.initState();
    dropdownValue = widget.defaultValue;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
          padding: const EdgeInsets.all(5.0),
          child: DropdownButtonHideUnderline(
            child: DropdownButton<String>(
              value: dropdownValue,
              items: widget.values.map((dropValue) {
                return DropdownMenuItem<String>(
                  value: dropValue,
                  child: Text(dropValue),
                );
              }).toList(),
              onChanged: (newDropdownValue) {
                setState(() {
                  dropdownValue = newDropdownValue!;
                });
                widget.onItemSelected(newDropdownValue);
              },
            ),
          ),
        ),
      ],
    );
  }
}

CodePudding user response:

Your problem is giving your dropdown a value that might be replicated in your list. Meaning that your list of strings might have multiple items with the same String. To solve this you need to give the value param a unique value. In this case you can use the array's index as in the code below.

I tried to replicate your code with dummy lists so I can run it and test it.

class _StarterPageState extends State<StarterPage> {
  Map<int, int?> dropdownValues = {};

  final List<String> _list = [
    'List Item 1',
    'List Item 2',
    'List Item 3',
    'List Item 4',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        physics: const ScrollPhysics(),
        itemCount: _list.length,
        itemBuilder: (_, parentListIndex) {
          List<String> selectedItemValues = <String>[
            'Dropdown Item 1',
            'Dropdown Item 2',
            'Dropdown Item 3',
            'Dropdown Item 4',
          ];

          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                padding: const EdgeInsets.all(5.0),
                child: DropdownButtonHideUnderline(
                  child: DropdownButton<int>(
                    hint: const Text('Select Item'), // Shown when value is null
                    // ?? (selectedItemValues.isNotEmpty ? 0 : null) => this adds a default value if the value is null
                    value: dropdownValues[parentListIndex] ?? (selectedItemValues.isNotEmpty ? 0 : null),
                    items: List.generate(
                      selectedItemValues.length,
                      (i) => DropdownMenuItem<int>(
                        value: i,
                        child: Text(selectedItemValues[i]),
                      ),
                    ),
                    onChanged: (newDropdownValue) {
                      setState(() {
                        dropdownValues[parentListIndex] = newDropdownValue!;
                        print('dropdown: ${dropdownValues[parentListIndex]}');
                      });
                    },
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

Moreover, since you have multiple dropdowns, you need to separate your dropdown values, so this is why I created a map that has the following structure:

{
   <parentListIndex>: <dropdownValueIndex>,
   <parentListIndex>: <dropdownValueIndex>,
  //...
}

You can add a hint param to your DropdownButton widget like so:

DropdownButton<int>(
   hint: const Text('Select Item'),
   value: dropdownValueIndex,
   //...
)

See live demo in this DartPad https://dartpad.dev/?id=08ace6b8f73cff0216673dcf9def433d

  • Related