I have a StatefulWidget where there is a ListView holding several childs widget.

One of the child is a GridView containing some items.

What I would want to achieve is to rebuild this GridView child when a button is pressed from the Parent widget. The button is located in the bottomNavigationBar in the Parent widget.

However, when I pressed the button, it should go to the _resetFilter() method, which works. But the setState() doesn't seem to update the GridView build() method inside Child widget.

class ParentState extends State<Parent> {

  // removed for brevity

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      bottomNavigationBar: BottomAppBar(
        child: new Row(
          children: <Widget>[
            Padding(
                padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0),
                child: SizedBox(
                  onPressed: () {
                    _resetFilter();
                  },
                )
            ),
          ],
        ),
      ),
      body: Container(
        child: Form(
            key: _formKey,
            child: ListView(
              children: <Widget>[
                Column(
                  children: <Widget>[
                    Container(
                      child: Column(
                        children: <Widget>[
                          Container(...), // this works
                          Column(...),
                          Container(...), // this works
                          Container( 
                            child: GridView.count(
                              // ...
                              children:
                                  List.generate(oriSkills.length, (int i) {

                                bool isSkillExist = false;

                                if (_selectedSkills.contains(rc.titleCase)) {
                                  isSkillExist = true;
                                } else {
                                  isSkillExist = false;
                                }
                                return Child( // this doesn't work
                                  id: oriSkills[i]['id'],
                                  name: oriSkills[i]['description'],
                                  skillSelect: isSkillExist, // this boolean showed correct value from the above logic
                                  onChange: onSkillChange,
                                );
                              }),
                            ),
                          ),
                        ],
                      ),
                    )
                  ],
                )
              ],
            )),
      ),
    );
  }

  void _resetFilter() {
    setState(() {
      _theValue = 0.0;
      searchC.text = "";
      _selectedSkills = []; // this is the variable that I'd like the GridView to recreate from.
    });
  }
}

I tried to print one of the field name inside Child widget, but it always showing the old value instead of the new one.

Even after presing the button, it does passing correct value to ChildState.

class ChildState extends State<Child> {
  final String name;
  final MyCallbackFunction onChange;
  bool skillSelect;
  double size = 60.0;

  ChildState({this.name, this.skillSelect, this.onChange});

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  void setSkillLevel() {
    setState(() {
      if (skillSelect) {
        skillSelect = false;
        onChange(name, false);
      } else {
        skillSelect = true;
        onChange(name, true);
      }
    });
  }

  Color _jobSkillSelect(bool select) {
    print(select); // always print old state instead of new state
    return select ? Color(MyColor.skillLvlOne) : Color(MyColor.skillDefault);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Column(children: <Widget>[
      InkResponse(
          onTap: setSkillLevel,
          child: Container(
            height: size,
            width: size,
            decoration: BoxDecoration(
              image: DecorationImage(
                colorFilter: ColorFilter.mode(_jobSkillSelect(skillSelect), BlendMode.color),
              ),
            ),
          )),
    ]));
  }
}

How can I update the Child widget to have the updated value from the Parent widget after reset button is pressed?


Solution 1: Robin Reiter

You might want to pass the values to the actual Child class. Not to its state.
The class is whats rebuilding once your parent rebuilds. So the new values will be reflected.

So your Child implementation should look something like this (don't forget to replace the onChange Type to your custom Function.

class Child extends StatefulWidget {
  final String name;
  final Function(void) onChange;
  final bool skillSelect;
  final double size;
  final Function(bool) onSkillLevelChanged;

  const Child({Key key, this.name, this.onChange, this.skillSelect, this.size, this.onSkillLevelChanged}) : super(key: key);

  @override
  _ChildState createState() => _ChildState();
}

class _ChildState extends State<Child> {
  Color _jobSkillSelect(bool select) {
    print(select); // always print old state instead of new state
    return select ? Color(MyColor.skillLvlOne) : Color(MyColor.skillDefault);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          InkResponse(
              onTap: () {
                if (widget.onSkillLevelChanged != null) {
                  widget.onSkillLevelChanged(!widget.skillSelect);
                }
              },
              child: Container(
                height: widget.size,
                width: widget.size,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    colorFilter: ColorFilter.mode(_jobSkillSelect(widget.skillSelect), BlendMode.color),
                  ),
                ),
              )),
        ],
      ),
    );
  }
}

In this case the Child ist not responsible anymore for managing its skillSelect property. It simply calls a Function on its parent. The parent then builds with a new skillSelect boolean.

So you might use this child like this:

return Child( // this doesn't work
   id: oriSkills[i]['id'],
   name: oriSkills[i]['description'],
   skillSelect: oriSkills[i]['isSkillExist'], 
   onChange: onSkillChange,
   onSkillLevelChanged: (newSkillLevel) {
      setState(() {
         oriSkills[i]['isSkillExist'] = newSkillLevel;
      });
   },
);