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

In Flutter, when you need to update the state of a widget from another widget, things can get a bit tricky. This is especially true when working with `StatefulWidget` instances, as they require explicit management of their own state.

Why Can’t I Just Call setState() on Another Widget’s State?

The reason you can’t just call `setState()` directly on another widget’s state from within your widget is due to the way Flutter handles state management. Each widget has its own, independent state that must be managed by itself.

When you try to access and modify another widget’s state, you’re essentially breaking the isolation between widgets, which can lead to unintended behavior and bugs in your app.

The Correct Way: Using Callbacks

The recommended approach for updating a `StatefulWidget`’s state from another widget is by using callbacks. This involves passing a function reference (or callback) from the parent widget to its child widget, allowing the child to notify the parent of any necessary updates.

Step 1: Add a Parameter Function on Child Widget

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

This involves adding a new parameter to your child widget’s constructor. In this case, we’re using a function reference called `notifyParent` that the parent can pass in.

Step 2: Create a Function on Parent Widget for Child to Callback

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

On your parent widget, create a new function (in this example, we’re calling it `refresh()`) that will be used by the child widget to notify the parent of any state updates. This function should call `setState()` to trigger an update in the parent widget.

Step 3: Pass Parent Function to Child Widget

new ChildWidget(notifyParent: refresh);

In your parent widget’s build method, pass the `refresh` function as a parameter to your child widget. This way, when the child needs to notify the parent of an update, it can simply call the provided function.

Step 4: Call Parent Function on Child Widget

widget.notifyParent();

In your child widget’s build method, call the `notifyParent` function (passed in from the parent) when necessary. This will trigger an update in the parent widget by calling its own `setState()` method.

Example Implementation

Here’s a complete example implementation to illustrate how this works:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Parent")),
      body: Center(
        child: Column(
          children: [
            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 {
  @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");
}

This example shows how you can call a method defined in the parent widget from within the child widget, and vice versa. By using callbacks to manage state updates between widgets, you can create complex UI flows that are easy to maintain.