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

StateFlutter

State Problem Overview


  1. For Example in the below code plus button works and able to update the text but the minus button does not.
  2. But if we press FloatingActionButton then the State is refreshed .
  3. The minus button is changing the value of the variable but not updating the state of parent widget .

enter image description here

here is code .....

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(title: 'Flutter Demo Home Page'),
    );
  }
}

int number;

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 {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    number = number ?? 0;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      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(() {
                    number = number + 1;
                  });
                },
              ),
              new Sub(),
            ],
          ),
        ],
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {
          setState(() {});
        },
        child: new Icon(Icons.update),
      ),
    );
  }
}

class Sub extends StatefulWidget {
  @override
  _SubState createState() => new _SubState();
}

class _SubState extends State<Sub> {
  @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: () {
        setState(() {
          number = number - 1;
        });
      },
    );
  }
}

State Solutions


Solution 1 - State

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

Solution 2 - State

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");
}

Solution 3 - State

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.

Solution 4 - State

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

Solution 5 - State

Old one but I would add my answer as per my findings:

var ancestralState = context.findAncestorStateOfType<ParentState>();
      ancestralState.setState(() {
        // here you can access public vars and update state.
        ...
      });

Solution 6 - State

Here is the solution which worked for me.

OUTPUT: State of Cart Widget is updated, upon addition of items.

enter image description here

Create a globalKey for the widget you want to update by calling the trigger from anywhere

final GlobalKey<CartWidgetState> cartKey = GlobalKey();

Make sure it's saved in a file have global access such that, it can be accessed from anywhere. I save it in globalClass where is save commonly used variables through the app's state.

class CartWidget extends StatefulWidget {

  CartWidget({Key key}) : super(key: key);
  @override
  CartWidgetState createState() => CartWidgetState();
}

class CartWidgetState extends State<CartWidget> {
  @override
  Widget build(BuildContext context) {
    //return your widget
    return Container();
  }
}

Call your widget from some other class.

class HomeScreen extends StatefulWidget {

  HomeScreen ({Key key}) : super(key: key);
  @override
  HomeScreenState createState() => HomeScreen State();
}

class HomeScreen State extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return ListView(
              children:[
                 ChildScreen(), 
                 CartWidget(key:cartKey)
              ]
            );
  }
}



class ChildScreen extends StatefulWidget {

  ChildScreen ({Key key}) : super(key: key);
  @override
  ChildScreenState createState() => ChildScreen State();
}

class ChildScreen State extends State<ChildScreen> {
  @override
  Widget build(BuildContext context) {
    return InkWell(
              onTap: (){
                // This will update the state of your inherited widget/ class
                if (cartKey.currentState != null)
                    cartKey.currentState.setState(() {});
              },
              child: Text("Update The State of external Widget"),
           );
  }
}

Solution 7 - State

class HomePage extends StatefulWidget {

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

class HomePageState extends State<HomePage> {

  int selectedIndex = 0;

   void setSelectedIndex(int index){
     setState(() {
      selectedIndex = index;
     });
  }
}

class TestPage extends StatefulWidget {

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

class TestPageState extends State<TestPage> {
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
     return  GestureDetector(
                      onTap: (){

                        final HomePageState state = context.findAncestorStateOfType<HomePageState>();

                        state.setSelectedIndex(4);

                      },
                    child: Container(
                        width: 100,
                        height: 100,
                        color: Colors.green
              )
     );
  }

}

Solution 8 - State

Although most of these previous answers will work, I suggest you explore the provider or BloC architectures, both of which have been recommended by Google.

In short, the latter will create a stream that reports to widgets in the widget tree whenever a change in the state happens and it updates all relevant views regardless of where it is updated from.

Here is a good overview you can read to learn more about the subject: https://bloclibrary.dev/#/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAjay KumarView Question on Stackoverflow
Solution 1 - StateMohamed ElrashidView Answer on Stackoverflow
Solution 2 - StateCopsOnRoadView Answer on Stackoverflow
Solution 3 - StateArnold PargeView Answer on Stackoverflow
Solution 4 - StateAlvin KondaView Answer on Stackoverflow
Solution 5 - StateAman BindlishView Answer on Stackoverflow
Solution 6 - StateVicky SalunkheView Answer on Stackoverflow
Solution 7 - Stateuser112380View Answer on Stackoverflow
Solution 8 - StateJacobo KoenigView Answer on Stackoverflow