2

I'm guessing I'm missing something simple but I've been at this for days trying every possible solution. How do we handle dropdown selection in a listview.builder? Is it possible to store dropdown button values in a List-String-?

I am creating a dynamic form based upon an XML templates and have dropdowns, checkboxes, input etc. EDIT: I dont know then what widgets are required until the XML has been parsed as the page loads. Hence the attempt to pass a dynamically created list of widgets to the Listview.builder.

Code below is an basic example failing to update.

Appreciate any advice here.

import 'package:flutter/material.dart';

class FormEG extends StatefulWidget {
  @override
  _FormEGState createState() => new _FormEGState();
}

class _FormEGState extends State<FormEG> {
  List<String> _listValues;
  List<DropdownMenuItem<String>> _items;
  List<Widget> _widgets;

  @override
  void initState() {
    _listValues = new List<String>();
    _listValues.add("b");

    _items = new List<DropdownMenuItem<String>>();
    _items.add(new DropdownMenuItem(child: Text("a"), value: "a"));
    _items.add(new DropdownMenuItem(child: Text("b"), value: "b"));
    _items.add(new DropdownMenuItem(child: Text("c"), value: "c"));
    _items.add(new DropdownMenuItem(child: Text("d"), value: "d"));

    _widgets = new List<Widget>();
    _widgets.add(new DropdownButton<String>(
      value: _listValues[0],
      items: _items,
      onChanged: (String newValue) {
        setState(() {
          _listValues[0] = newValue;
        });
      },
    ));
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: new ListView.builder(
          itemCount: _widgets.length,
          itemBuilder: (context, index) {
          return _widgets[index];
          },
        ));
      }
  }
plam
  • 313
  • 1
  • 4
  • 17
  • Your _widgets[0] is not getting updated, its having same button on each setState. SetState() call the Build method again and Button is Drawn again but in your Case , your are having static Button assign at _widgets[index]. You have to move your Widget Drawing Method in Build Method instead of InitState. – anmol.majhail Dec 06 '18 at 08:32

3 Answers3

1

I ended up wrapping each dropdown widget (in List<Widget>) with a statefulbuilder so when setstate is called from each widget only that widget is updated but one can still udate the entire tree if needed. In this way setState is called from within a build as per anmol.majhail comments.

import 'package:flutter/material.dart';

class FormEG extends StatefulWidget {
  @override
  _FormEGState createState() => new _FormEGState();
}

class _FormEGState extends State<FormEG> {
  List<String> _listValues;
  List<DropdownMenuItem<String>> _items;
  List<Widget> _widgets;

  @override
  void initState() {
    _listValues = new List<String>();
    setDefaults();
    setWidgets();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print("built");
    return new Scaffold(
        body: new ListView.builder(
          itemCount: _widgets.length,
          itemBuilder: (context, index) {
        return _widgets[index];
      },
    ));
  }

  void setDefaults() {
    _listValues.add("b");
  }

  void setWidgets() {
    setMenuItems();
    _widgets = new List<Widget>();
    _widgets.add(
      new StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return new DropdownButton<String>(
            value: _listValues[0],
            items: _items,
            onChanged: (String newValue) {
              setState(() {
                _listValues[0] = newValue;
              });
            },
          );
        },
      ),
    );
  }

  void setMenuItems() {
    _items = new List<DropdownMenuItem<String>>();
    _items.add(new DropdownMenuItem(child: Text("a"), value: "a"));
    _items.add(new DropdownMenuItem(child: Text("b"), value: "b"));
    _items.add(new DropdownMenuItem(child: Text("c"), value: "c"));
    _items.add(new DropdownMenuItem(child: Text("d"), value: "d"));
  }
}
plam
  • 313
  • 1
  • 4
  • 17
0

Following Code Works Well. Because SetSate Calls the Build method Again & DropdownButton is Drawn again. In your Code DropdownButton is placed at _widgets[0] value & this Value Stays Static through the code cycle. Even after Calling SetState it will remain same - because SetState wont update _widgets[0] value - it will only call Build Method Again.

@override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: new ListView.builder(
      itemCount: _widgets.length,
      itemBuilder: (context, index) {
        return DropdownButton<String>(
          value: _listValues[0],
          items: _items,
          onChanged: (newValue) {
            setState(() {
              _listValues[0] = newValue;
            });
          },
        );
      },
    ));
  }
anmol.majhail
  • 48,256
  • 14
  • 136
  • 105
  • Thanks anmol.majhail. Unfortunately my listview is dynamic based upon the contents of xml files so I dont know what widgets are required until the xml is read and therefore am not able to hard code widgets into 'build'. The hope was to create a list of widgets dynamically based upon the xml contents and then feed this to listview.builder. I am guessing then given that widgets[0] is static this is not possible and I instead need to re-create the widget list under set state every time. – plam Dec 06 '18 at 11:10
  • Yes - initially you can create a list of widgets dynamically based upon the xml contents and then feed this to listview.builder - But In - order to Show the Changes in screen UI OR Widget you need to feed the Widget list new Value Or Update the value in already build List. – anmol.majhail Dec 06 '18 at 11:46
  • And the only way to feed the widget list a new value/update is to regenerate the list and rebuild the entire tree?!? – plam Dec 06 '18 at 11:57
  • setState does the same job but for the Build Method, But in your Case Widgets are in List so you need to manually update the list values. – anmol.majhail Dec 06 '18 at 12:06
0

You could try just using a List and update the List dynamically and then setState.

ListView(
                  shrinkWrap: true,
                  scrollDirection: Axis.vertical,
                  children: dropDownList),
            ),

Then update it based on a trigger/event.

 _buildDropDownList() {
     if (dropDownEnabled) {
       setState (() {
         dropDownList.addAll([1,2,,4,5])
       });
       } else {
        setState (() {
         dropDownList = [];
      });   
    }
Yonkee
  • 1,781
  • 3
  • 31
  • 56