How to Set/Update State of StatefulWidget from other StatefulWidget in Flutter?

1.On Child Widget : add parameter Function paramter

class ChildWidget extends StatefulWidget {
  final Function() notifyParent;
  ChildWidget({Key key, @required this.notifyParent}) : super(key: key);
}

2.On Parent Widget : create a Function for the child to callback

refresh() {
  setState(() {});
}

3.On Parent Widget : pass parentFunction to Child Widget

new ChildWidget( notifyParent: refresh );  

4.On Child Widget : call the Parent Function

  widget.notifyParent();

Screenshot (Parent to child, Child to parent):

enter image description here

This examples shows calling a method

  1. Defined in Child widget from Parent widget.
  2. Defined in Parent widget from Child widget.

Code:

class ParentPage extends StatelessWidget {
  final GlobalKey<ChildPageState> _key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Parent")),
      body: Center(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Container(
                color: Colors.grey,
                width: double.infinity,
                alignment: Alignment.center,
                child: ElevatedButton(
                  child: Text("Call method in child"),
                  onPressed: () => _key.currentState!.methodInChild(), // calls method in child
                ),
              ),
            ),
            Text("Above = Parent\nBelow = Child"),
            Expanded(
              child: ChildPage(
                key: _key,
                function: methodInParent,
              ),
            ),
          ],
        ),
      ),
    );
  }

  methodInParent() => Fluttertoast.showToast(msg: "Method called in parent", gravity: ToastGravity.CENTER);
}

class ChildPage extends StatefulWidget {
  final VoidCallback function;

  ChildPage({Key? key, required this.function}) : super(key: key);

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

class ChildPageState extends State<ChildPage> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.teal,
      width: double.infinity,
      alignment: Alignment.center,
      child: ElevatedButton(
        child: Text("Call method in parent"),
        onPressed: () => widget.function(), // calls method in parent
      ),
    );
  }

  methodInChild() => Fluttertoast.showToast(msg: "Method called in child");
}

Old Solutions:

  1. Create a global instance of _MyHomePageState. Use this instance in _SubState as _myHomePageState.setState
  2. No need to create a global instance. Instead, just pass the parent instance to the child widget

New Solution: Passing a callback seems the better solution than above solutions

CODE UPDATED AS PER FLUTTER 2.0:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
    );
  }
}

EdgeInsets globalMargin =
    const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0);
TextStyle textStyle = const TextStyle(
  fontSize: 100.0,
  color: Colors.black,
);

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int number = 0;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('SO Help'),
      ),
      body: new Column(
        children: <Widget>[
          new Text(
            number.toString(),
            style: textStyle,
          ),
          new GridView.count(
            crossAxisCount: 2,
            shrinkWrap: true,
            scrollDirection: Axis.vertical,
            children: <Widget>[
              new InkResponse(
                child: new Container(
                    margin: globalMargin,
                    color: Colors.green,
                    child: new Center(
                      child: new Text(
                        "+",
                        style: textStyle,
                      ),
                    )),
                onTap: () {
                  setState(() {
                    this.number++;
                  });
                },
              ),
              new Sub(onTap: () {
                setState(() {
                  this.number--;
                });
              }),
            ],
          ),
        ],
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {
          setState(() {});
        },
        child: new Icon(Icons.update),
      ),
    );
  }
}

class Sub extends StatelessWidget {
  final Function onTap;
  Sub({this.onTap});

  @override
  Widget build(BuildContext context) {
    return new InkResponse(
      child: new Container(
        margin: globalMargin,
        color: Colors.red,
        child: new Center(
          child: new Text(
            "-",
            style: textStyle,
          ),
        ),
      ),
      onTap: this.onTap,
    );
  }
}

Just let me know if it works.


I would like to extend Mohamed Elrashid answer, in case you require to pass a variable from the child widget to the parent widget

On child widget:

class ChildWidget extends StatefulWidget {
  final Function() notifyParent;
  ChildWidget({Key key, @required this.notifyParent}) : super(key: key);
}

On parent widget

void refresh(dynamic childValue) {
  setState(() {
    _parentVariable = childValue;
  });
}

On parent widget: pass the function above to the child widget

new ChildWidget( notifyParent: refresh ); 

On child widget: call the parent function with any variable from the the child widget

widget.notifyParent(childVariable);