0

I'm making a page which contains some dropboxes which interact each other and a Futurebuilder wrapped ListView.

I'm calling 'setState' when Dropbox changes and checkbox is clicked. However, because of the setState in checkbox's onChanged, the Futurebuilder keeps being called and the ListView is rebuilded. Therefore the entire Listview is blinkning when checkbox is clicked like the video below.

Problem Video

I want to keep the Listview and update only checkbox. Is there anyone who can help me? Thank you.

The full code is

class _StatefulDialogWidgetState extends State<StatefulDialogWidget> {
  ....   

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // Dropdown's
        Dropdown(0, widget.ReceiveArgs, _lListOfDepthList),
        Dropdown(1, widget.ReceiveArgs, _lListOfDepthList),
        Dropdown(2, widget.ReceiveArgs, _lListOfDepthList),
        Dropdown(3, widget.ReceiveArgs, _lListOfDepthList),
        Dropdown(4, widget.ReceiveArgs, _lListOfDepthList),
        
        // Listview with FutureBuilder
        AptListview(),
      ],
    );
  }

ListView Code

Widget AptListview() {
    return FutureBuilder<List<String>>(
        future: AptNameListView(widget.ReceiveArgs),
        builder: (context, snapshot) {
          if (_bLastDepth == false) {
            return Text("Select Address");
          } else {
            if (snapshot.hasData == false || snapshot.data.isEmpty == true) {
              return CircularProgressIndicator();
            } else {
              return Expanded(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: _AptNameList.length,
                  itemBuilder: (context, index) {
                    //return new Text("${_AptName[index]}");
                    return CheckboxListTile(
                      title: Text(_AptNameList[index]),
                      value: _isAptChecked[index],
                      onChanged: (value) {
                        setState(() {                   //  SetState in FutureBuilder
                          _isAptChecked[index] = value;                              
                        });
                      },
                    );
                  },
                ),
              );
            }
          }
        });
  }

Dropdown Code

Widget Dropdown(int nDepth, ArgumentClass ReceiveArgs,
      List<List<String>> ListOfDepthList) {
    String _Value = "";
    List<DropdownMenuItem<String>> _itemList = null;
    if (ListOfDepthList.length <= nDepth) {
      _Value = "";
      _itemList = null;
    } else {
      _Value = _SelectedAddressList[nDepth];
      _itemList = GetMainItem(ListOfDepthList[nDepth]);
    }
    return DropdownButton(
        value: _Value,
        items: _itemList,
        onChanged: (value) {
          if (value.compareTo(GlobalObject().startMessage) != 0) {
            setState(() {
              .....
              // setState in Dropdown
            });
          }
        });
  }
chan hong Park
  • 45
  • 2
  • 10

2 Answers2

4

Please read the FutureBuilder documentation. It states:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

As the documentation states, obtain the future in initState and store it in your state.

Future future;
@override
void initState() {
  future = AptNameListView(widget.ReceiveArgs);
  super.initState();
}


Widget AptListview() {
    return FutureBuilder<List<String>>(
        future: future,
        builder: (context, snapshot) {
          if (_bLastDepth == false) {
            return Text("Select Address");
          } else {
            if (snapshot.hasData == false || snapshot.data.isEmpty == true) {
              return CircularProgressIndicator();
            } else {
              return Expanded(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: _AptNameList.length,
                  itemBuilder: (context, index) {
                    //return new Text("${_AptName[index]}");
                    return CheckboxListTile(
                      title: Text(_AptNameList[index]),
                      value: _isAptChecked[index],
                      onChanged: (value) {
                        setState(() {                   //  SetState in FutureBuilder
                          _isAptChecked[index] = value;                              
                        });
                      },
                    );
                  },
                ),
              );
            }
          }
        });
  }
Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
  • Well... If I do that, the Listview in FutureBuilder is built once when the Widget is created. I want to refresh the Listview according to other dropboxes' change. Here the trick thing is the Listview has Checkbox, so it needs to call 'setState' when it ticks. – chan hong Park Feb 19 '21 at 12:41
1

I solve this problem by

  1. Call the future function in dropbox's setState
  2. Change the Futurebuilder to Listview.

It prevented updating of listview in futurebuilder when SetState is called.

chan hong Park
  • 45
  • 2
  • 10