[Solved] Force Flutter navigator to reload state when popping

Question

Asked by bartektartanus on April 12, 2018 (source).

I have one StatefulWidget in Flutter with button, which navigates me to another StatefulWidget using Navigator.push(). On second widget I'm changing global state (some user preferences). When I get back from second widget to first, using Navigator.pop() the first widget is in old state, but I want to force it's reload. Any idea how to do this? I have one idea but it looks ugly:

  1. pop to remove second widget (current one)
  2. pop again to remove first widget (previous one)
  3. push first widget (it should force redraw)

Answer

Question answered by rmtmckenzie (source).

There's a couple of things you could do here. @Mahi's answer while correct could be a little more succinct and actually use push rather than showDialog as the OP was asking about. This is an example that uses Navigator.push:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: Text('back'),
          ),
        ],
      ),
    );
  }
}

class FirstPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new FirstPageState();
}

class FirstPageState extends State<FirstPage> {

  Color color = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color,
      child: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("next"),
            onPressed: () async {
              final value = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => SecondPage()),
                ),
              );
              setState(() {
                color = color == Colors.white ? Colors.grey : Colors.white;
              });
            },
          ),
        ],
      ),
    );
  }
}

void main() => runApp(
      MaterialApp(
        builder: (context, child) => SafeArea(child: child),
        home: FirstPage(),
      ),
    );

However, there's another way to do this that might fit your use-case well. If you're using the global as something that affects the build of your first page, you could use an InheritedWidget to define your global user preferences, and each time they are changed your FirstPage will rebuild. This even works within a stateless widget as shown below (but should work in a stateful widget as well).

An example of inheritedWidget in flutter is the app's Theme, although they define it within a widget instead of having it directly building as I have here.

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              ColorDefinition.of(context).toggleColor();
              Navigator.pop(context);
            },
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class ColorDefinition extends InheritedWidget {
  ColorDefinition({
    Key key,
    @required Widget child,
  }): super(key: key, child: child);

  Color color = Colors.white;

  static ColorDefinition of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ColorDefinition);
  }

  void toggleColor() {
    color = color == Colors.white ? Colors.grey : Colors.white;
    print("color set to $color");
  }

  @override
  bool updateShouldNotify(ColorDefinition oldWidget) =>
      color != oldWidget.color;
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var color = ColorDefinition.of(context).color;

    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                );
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(
              child: new ColorDefinition(child: child),
            ),
        home: new FirstPage(),
      ),
    );

If you use inherited widget you don't have to worry about watching for the pop of the page you pushed, which will work for basic use-cases but may end up having problems in a more complex scenario.

DART FLUTTER NAVIGATION
SHARE: