In a Nested Navigator Structure of Flutter, How do you get the a Specific Navigator?

Archie G. Quiñones picture Archie G. Quiñones · Mar 18, 2019 · Viewed 14.2k times · Source

I have this problem when I have Nested Navigators. So something like,

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: "/",
      routes: {
        '/': (context) => SomeOneView(),
        '/two': (context) => SomeTwoView(),
        '/three': (context) => SomeThreeView(),
      },
    );
  }
}

class SomeOneView extends StatefulWidget {
  @override
  _SomeOneViewState createState() => _SomeOneViewState();
}

class _SomeOneViewState extends State<SomeOneView> {
  @override
  Widget build(BuildContext context) {
   return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.indigo,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            color: Colors.white,
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('/two'),
          ),
        ],
      ),
    );
  }
}



class SomeTwoView extends StatefulWidget {

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

class _SomeTwoViewState extends State<SomeTwoView> {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        // Some implementation
      },
      child: Navigator(
        initialRoute: "two/home",
        onGenerateRoute: (RouteSettings settings) {
          WidgetBuilder builder;
          switch (settings.name) {
            case "two/home":
              builder = (BuildContext context) => HomeOfTwo();
              break;
            case "two/nextpage":
              builder = (BuildContext context) => PageTwoOfTwo();
              break;
          }
          return MaterialPageRoute(builder: builder, settings: settings);
        },
      ),
    );
  }
}

class HomeOfTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.white,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            color: Colors.white,
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('two/nextpage'),
          ),
        ],
      ),
    );
  }
}

class PageTwoOfTwo extends StatelessWidget {

   @override
   Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.teal,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          MaterialButton(
            child: Text('Next'),
            onPressed: () => Navigator.of(context).pushNamed('/three'),
          ),
        ],
      ),
    );
  }
}

So as you can see, I navigate from the Top Most Navigator provided by MaterialApp going down to the child Navigator's 'two/nextpage' which should then go to MaterialApp '/three'. The problem is that doing onPressed: () => Navigator.of(context).pushNamed('/three'), returns the Navigator of the current context which is the child Navigator. I need to access the MaterialApp's Navigator to navigate correctly. What is the right way to do this?

Also how to handle the case where the Navigator I want to access is somewhere in the middle of a stack of Navigators?

Answer

R&#233;mi Rousselet picture Rémi Rousselet · Mar 18, 2019

Most of the time, you'll have only 2 Navigator.

Which means to obtain the nested one, do:

Navigator.of(context)

And to obtain the root one do:

Navigator.of(context, rootNavigator: true)

For more complex architecture, the easiest by far is to use GlobalKey (since you'll never read Navigators during build)

final GlobalKey<NavigatorState> key =GlobalKey();
final GlobalKey<NavigatorState> key2 =GlobalKey();

class Foo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: key,
      home: Navigator(
        key: key2,
      ),
    );
  }
}

Which you can then use this way:

key.currentState.pushNamed('foo')