Asked  3 Months ago    Answers:  5   Viewed   484 times

I am trying to create a dropdown button in Flutter. I am getting a List from my database then I pass the list to my dropdownButton everything works the data is shown as intended but when I choose an element from it I get this error:

There should be exactly one item with [DropdownButton]'s value: Instance of 'Tag'. 
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 805 pos 15: 'items == null || items.isEmpty || value == null ||
          items.where((DropdownMenuItem<T> item) {
            return item.value == value;
          }).length == 1'

I tried setting DropdownButton value to null it works but then I can't see the chosen element.

Here is my code:

FutureBuilder<List<Tag>>(
    future: _tagDatabaseHelper.getTagList(),
    builder: (BuildContext context, AsyncSnapshot<List<Tag>> snapshot) {
      if (!snapshot.hasData) {
        return Center(
          child: CircularProgressIndicator(),
        );
      }
      return ListView(
        children: <Widget>[
          SizedBox(
            height: MediaQuery.of(context).size.height * 0.2,
          ),
          Container(
            margin: EdgeInsets.symmetric(
                horizontal: MediaQuery.of(context).size.width * 0.07),
            child: Theme(
              data: ThemeData(canvasColor: Color(0xFF525A71)),
              child: DropdownButton<Tag>(
                value: _selectedTag,
                isExpanded: true,
                icon: Icon(
                  Icons.arrow_drop_down,
                  size: 24,
                ),
                hint: Text(
                  "Select tags",
                  style: TextStyle(color: Color(0xFF9F9F9F)),
                ),
                onChanged: (value) {
                  setState(() {
                    _selectedTag = value;
                  });
                },
                items: snapshot.data.map((Tag tag) {
                  return DropdownMenuItem<Tag>(
                    value: tag,
                    child: Text(
                      tag.tagTitle,
                      style: TextStyle(color: Colors.white),
                    ),
                  );
                }).toList(),
                value: _selectedTag,
              ),
            ),
          ),

I used futureBuilder to get my List from database.

 Answers

47

Well, since no problem has an exact same solution. I was facing the same issue with my code. Here is How I fixed this.

CODE of my DropdownButton:

DropdownButton(
   items: _salutations
         .map((String item) =>
             DropdownMenuItem<String>(child: Text(item), value: item))
         .toList(),
    onChanged: (String value) {
       setState(() {
         print("previous ${this._salutation}");
         print("selected $value");
         this._salutation = value;
            });
          },
     value: _salutation,
),

The Error

In the code snippet below, I am setting the state for a selection value, which is of type String. Now problem with my code was the default initialization of this selection value. Initially, I was initializing the variable _salutation as:

String _salutation = ""; //Notice the empty String.

This was a mistake!

Initial selection should not be null or empty as the error message correctly mentioned.

'items == null || items.isEmpty || value == null ||

And hence the crash:

crash_message

Solution
Initialize the value object with some default value. Please note that the value should be the one of the values contained by your collection. If it is not, then expect a crash.

  String _salutation = "Mr."; //This is the selection value. It is also present in my array.
  final _salutations = ["Mr.", "Mrs.", "Master", "Mistress"];//This is the array for dropdown
Thursday, July 29, 2021
 
cyber_truite
answered 3 Months ago
83

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

Saturday, August 21, 2021
 
dimitarvp
answered 2 Months ago
34

Technically it's just a Spinner with custom views and styles.

I tried to make one that looks similar to the one you posted, using AppCompat, working with custom drawables and with the view's elevation property, thus it may not completely work for Android versions older than 5.0.

First let us define our Spinner with its dropdown properties:

<your.package.CustomSpinner
    android:id="@+id/spinner"
    style="@style/Widget.AppCompat.Spinner"
    android:layout_margin="10dp"
    android:layout_width="200dp"
    android:dropDownWidth="200dp"
    android:layout_height="?attr/dropdownListPreferredItemHeight"
    android:dropDownVerticalOffset="?attr/dropdownListPreferredItemHeight"
    android:background="@drawable/spinner_bg"
    android:popupBackground="@android:color/white"
    android:paddingRight="14dp"
    android:stateListAnimator="@drawable/spinner_sla"
    android:popupElevation="3dp" />

Important: We use the CustomSpinner class from this post, because we need the callbacks to know when the spinner opens an closes (for styling purposes).

Then we setup the spinner: We use a custom View for the selected item (layout defined below) to apply our styles, and AppCompat's default R.layout.support_simple_spinner_dropdown_item, but we may use another layout to further adjust the styling.

String[] data = {"Arial", "Calibri", "Helvetica", "Roboto", "Veranda"};

ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item_selected, data);
adapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);

final CustomSpinner spinner = (CustomSpinner) view.findViewById(R.id.spinner);
spinner.setAdapter(adapter);
spinner.setSpinnerEventsListener(new CustomSpinner.OnSpinnerEventsListener() {
    public void onSpinnerOpened() {
        spinner.setSelected(true);
    }
    public void onSpinnerClosed() {
        spinner.setSelected(false);
    }
});

And here the spinner_item_selected.xml layout:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?attr/spinnerDropDownItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="?attr/dropdownListPreferredItemHeight"
    android:background="@drawable/abc_spinner_mtrl_am_alpha"
    android:ellipsize="marquee" />

Further we need the drawables used above:

spinner_bg.xml as the spinner's background:

<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:exitFadeDuration="@android:integer/config_mediumAnimTime">
    <item android:state_pressed="true" android:drawable="@android:color/white" />
    <item android:state_selected="true" android:drawable="@android:color/white" />
    <item>
        <inset android:insetLeft="-1dp" android:insetRight="-1dp">
            <shape android:shape="rectangle">
                <stroke android:width="1dp" android:color="#cccccc" />
                <solid android:color="#f0f0f0" />
            </shape>
        </inset>
    </item>
</selector>

spinner_sla.xml to animate the spinner's elevation:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <set>
            <objectAnimator
                android:duration="@android:integer/config_mediumAnimTime"
                android:propertyName="translationZ"
                android:valueTo="3dp" />
        </set>
    </item>
    <item android:state_selected="true">
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="3dp" />
        </set>
    </item>
    <item>
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="0" />
        </set>
    </item>
</selector>

This gives us a result like this (left collapsed, right open):

enter image description here

If we want to use a spinner with images, we would also have to define a custom dropdown item view.

Sunday, September 12, 2021
 
wenjiehu
answered 1 Month ago
31

The error is because you are declaring a method variable newValue you must declare that variable as global inside your StatefulWidget.

   String newValue;

  Widget buildDropdownButton() {
  return new Padding(
    padding: const EdgeInsets.all(24.0),
    child: new Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        new ListTile(
          title: const Text('Frosting'),
          trailing: new DropdownButton<String>(
              hint: Text('Choose'),
              onChanged: (String changedValue) {
                newValue=changedValue;
                setState(() {
                  newValue;
                  print(newValue);
                });
              },
              value: newValue,
              items: <String>['None', 'Chocolate', 'Vanilla', 'ButterCream']
                  .map((String value) {
                return new DropdownMenuItem<String>(
                  value: value,
                  child: new Text(value),
                );
              }).toList()),
        ),
      ],
    ),
  );
  }
Tuesday, September 14, 2021
 
daiscog
answered 1 Month ago
63

Well, mostly we do sessions -- send a single cookie with an identifier for the user, and store all the option values on the server. But if I really didn't want to do a session for some reason, I suppose I'd probably do the single cookie on account of it creating less network traffic if done properly.

Saturday, September 18, 2021
 
user113716
answered 1 Month ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :