如何在Flutter中实现下拉列表?

时间:2019-04-08 10:28:27

标签: android ios dart flutter

我正在尝试从REST API加载数据并将其加载到下拉列表中。下面是实现我的下拉列表和从外部加载数据的代码。

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();
            }
          },
        );
      }

下面是上述方法的使用方式

@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()
                      ],
                    ),
                  ),

在上面的代码中,我得到了数据,那部分就好了。我也可以将它们加载到下拉列表中。问题是,只要我从下拉菜单中选择一项,就会触发以下错误。

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): 

我还注意到有时候,下拉列表中填充有重复的值,例如数据加载部分已经进行了两次。

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

我真的很想在编写此答案之前测试您的代码,但我不能,因为获取数据请求网址,所以我决定在黑暗中拍摄。

我对您的源代码进行了一些更改,以避免多个Web请求获得下拉选项,因为您正在使用setState重建窗口小部件树,并且您的Web请求位于您的build方法中,这不是一个好主意。来源中有一些评论,因此...如果不起作用,请随时分享结果。

//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(),
  );
}