'How to implement a Drop Down list in Flutter?

I am trying to load data from my REST API and load them to the dropdown lists. Below is the code where my dropdown list and data loading from outside is implemented.

String _selectedLocation;

    FutureBuilder _dropDownMenu() {
         List<String> categoryList = new List<String>();

        return FutureBuilder<List<ProductCategory>>(
          future: DataFetch().fetchCategoryList(
              AppNavigation.getAPIUrl() + "productCategory/getAllProductCategories",
              ProductCategory),
          builder: (context, snapshot) {
            if (snapshot.hasError) print(snapshot.error);

            if (snapshot.hasData) {
              for (int i = 0; i < snapshot.data.length; i++) {
                categoryList.add(snapshot.data[i].categoryName);
              }

              return DropdownButton(
                hint: Text('Please choose'), // Not necessary for Option 1
                value: _selectedLocation,
                onChanged: (newValue) {
                  setState(() {
                    _selectedLocation = newValue;
                  });
                },
                items: categoryList.map((data) {
                  return DropdownMenuItem(
                    child: new Text(data),
                    value: data,
                  );
                }).toList(),
              );
            } else {
              return CircularProgressIndicator();
            }
          },
        );
      }

Below is how the above method is used

@override
      Widget build(BuildContext context) {
        return Scaffold(
            body: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
                expandedHeight: 200.0,
                centerTitle: true,
                floating: true,
                pinned: true,
                flexibleSpace: FlexibleSpaceBar(
                  background: Image.asset(
                      "assets/images/create_sell_ad_background_2_dark2.jpg",
                      fit: BoxFit.fill),
                  title: Text("I want to sell.."),
                ),
                actions: <Widget>[
                  FlatButton(
                    child: Text(
                      "Save",
                      style: TextStyle(
                          color: Colors.white,
                          fontSize: 15,
                          fontWeight: FontWeight.bold),
                    ),
                    onPressed: () {/* ... */},
                  ),
                ]),
            new SliverPadding(
              padding: new EdgeInsets.all(16.0),
              sliver: new SliverList(
                delegate: new SliverChildListDelegate([
                  Container(
                    padding: EdgeInsets.all(20),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        _buildInputLabel("Select the Category"),
                        _dropDownMenu()
                      ],
                    ),
                  ),

In the above code, I get the data and that part is fine. I can also load them to the drop down. The issue is whenever I selected an item from the dropdown, below error is triggered.

I/flutter ( 8467): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 8467): The following assertion was thrown building FutureBuilder<List<ProductCategory>>(dirty, state:
I/flutter ( 8467): _FutureBuilderState<List<ProductCategory>>#176ad):
I/flutter ( 8467): 'package:flutter/src/material/dropdown.dart': Failed assertion: line 560 pos 15: 'items == null ||
I/flutter ( 8467): items.isEmpty || value == null || items.where((DropdownMenuItem<T> item) => item.value ==
I/flutter ( 8467): value).length == 1': is not true.
I/flutter ( 8467): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 8467): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 8467): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 8467):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 8467): When the exception was thrown, this was the stack:
I/flutter ( 8467): #2      new DropdownButton 
I/flutter ( 8467): #3      _CreateSellingAdvertisementState._dropDownMenu.<anonymous closure>
I/flutter ( 8467): #4      _FutureBuilderState.build (package:flutter/src/widgets/async.dart)
I/flutter ( 8467): #5      StatefulElement.build 
I/flutter ( 8467): #6      ComponentElement.performRebuild 
I/flutter ( 8467): #7      Element.rebuild 
I/flutter ( 8467): #8      BuildOwner.buildScope 
I/flutter ( 8467): #9      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame 
I/flutter ( 8467): #10     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback 
I/flutter ( 8467): #11     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback 
I/flutter ( 8467): #12     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame 
I/flutter ( 8467): #13     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame 
I/flutter ( 8467): #17     _invoke (dart:ui/hooks.dart:209:10)
I/flutter ( 8467): #18     _drawFrame (dart:ui/hooks.dart:168:3)
I/flutter ( 8467): (elided 5 frames from class _AssertionError and package dart:async)
I/flutter ( 8467): 

I also noticed that sometimes, the dropdown is filled with repeated values, like the data loading part has taken twice.

How can I solve this problem?



Solution 1:[1]

I really wanted test your code before I write this answer but I cant because fetch data request Url so I decide to shot in the dark.

I make some changes in your source code to avoid multiple web request to get dropdown options as you're using setState to rebuild your widget tree and your web request is inside your build method which is not a good idea. The source has some comments so... if this does't work please feel free to share the results.

//This is the state of your class
/// Is a good practice initialize the selection value.
/// I'am doing this after dataFetch is completed.
String _selectedLocation;

/// we make the future object part of the state to avoid data fetching
/// from web every time that build method is called·
Future< List<ProductCategory> > _future;
/// and now we store the category list as cache in widget state
List<String> _categoryList;

initState(){
  // in initState we trigger the network call for load the dropdown menu options.
  // this is part of trying to avoid recall data fetching from network every time
  // that we need rebuild the widget.
  _future = DataFetch().fetchCategoryList(
      AppNavigation.getAPIUrl() + "productCategory/getAllProductCategories",
      ProductCategory);
}

Widget _dropDownMenu() {
  // if we haven't load the options from network yet... we await the future
  // completes to create dropdown menu.
  if (_categoryList == null) {
    return FutureBuilder<List<ProductCatefory>>(
        future: _future,
        builder: (context, snapshot) {
          if (snapshot.hasError)
            print(snapshot.error);

          else if (snapshot.hasData) {
            for (int i = 0; i < snapshot.data.length; i++)
              _categoryList.add(snapshot.data[i].categoryName);

            // I put this line just to grant that the initial option of
            //dropdown menu has some valid value. You can erase if not needed.
            _selectedLocation = snapshot.data[0].categoryName);
            return _createDropDownMenu();
          }
          else return CircularProgressIndicator();
        },);
  }
  // other way if we already load the options data we just create the dropdown menu
  // we just populate the dropdown menu options with _categoryList values.
  return _createDropDownMenu();
}

Widget _createDropDownMenu(){
  return DropdownButton<String>(
    hint: Text('Please choose'), // Not necessary for Option 1
    value: _selectedLocation,
    onChanged: (newValue) {
      setState(() {
        _selectedLocation = newValue;
      });
    },
    items: _categoryList.map((data) {
      return DropdownMenuItem<String>(
          child: new Text(data),
          value: data,
      );
    }).toList(),
  );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1