Nested Navigation In Flutter : Navigator Widget

Nested Navigation In Flutter : Navigator Widget

Learn more about how implement nested navigation in Flutter apps using the Navigator widget

Introduction

A Navigator widget in Flutter is what we use to maintain a stack of routes and it plays a huge role in helping us to navigate between routes. The Navigator widget often works under the hood without us having to initialise it but there are certain use cases where we may have to do it manually. Most Flutter apps tend to use methods associated with Navigator like push and pop to move to other screens instead of defining it. This is because when we start our app with the MaterialApp widget, it will introduce a Navigator for us to use. In this article, we shall explore more about using multiple Navigators / Nested Navigation.

Nested navigation is mostly needed when we don't want to completely replace the current screen with a new screen. In such cases, what we'll be doing is pushing a screen only at certain intervals and keeping the rest of the screen intact. For example, the Instagram app.

In this article, you will be learning more about:

  • Using multiple Navigator widgets
  • Performing Nested navigation
  • Using keys to access the desired Navigator

Setting Up

  • Let's start with an example where we have only one Navigator widget. Our class will have a BottomNavigationBar wrapped inside a MaterialApp and a NestedScreen() which acts like the body of the BottomNavigationBar. To go forward, we will pass a navigator calledNavigatorKeys.navigatorKeyMainto theMaterialApp`. Refer to the code snipper given below to understand this better:
class NestedScreen extends StatelessWidget {
  NestedScreen({Key key}) : super(key: key);

  void _push(BuildContext context, String name) {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Scaffold(
          appBar: AppBar(
            ...
          ),
          body: Center(
            ...
          ),
        ),
      ),
    );

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
         body: _home(context)
       );
  }

  Widget _home(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            GestureDetector(
              onTap: () => _push(context, 'Page 1'),
              child: Text(
                'Page A',
                style: TextStyle(fontSize: 50),
              ),
            ),
            GestureDetector(
              onTap: () => _push(context, 'Page 2'),
              child: Text(
                'Page B',
                style: TextStyle(fontSize: 50),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Here’s the full source code .

With one Navigator widget

And this is how our screen looks like:

Nested_Nav_Eg.png

If we click on Page A or Page B then we will notice that a new screen Nested Screen replaces the current screen, which is definitely not what we wanted.

Nested_nav_gif_1.gif

With two Navigator widgets

  • Having two widgets can potentially be a hassle. So, to solve this problem we will have to introduce a new Navigator in the widget tree after the BottomNavigatorBar widget and use it which will cause the Navigator to only replace the body of the current screen. First lets add a function _routeBuilders() to our NestedScreen class and a GlobalKey variable as shown below:
class NestedScreen extends StatelessWidget {
  NestedScreen({this.navigatorKey, Key key}) : super(key: key);
  final GlobalKey<NavigatorState> navigatorKey;
   ...
Map<String, WidgetBuilder> _routeBuilders( BuildContext context ) {
    return {
      '/': (context) => _home(context),
      /// we only have one screen for now
    };
  }
   ...
}
  • The function given above will manage routes for us.(Here we only have one screen) Now, add a new Navigator widget in the body of NestedScreen class as shown below:
class NestedScreen extends StatelessWidget {
   ...
@override
  Widget build(BuildContext context) {
    var routeBuilders = _routeBuilders(context);

    return Scaffold(
      body: Navigator(
        key: navigatorKey,
        initialRoute: NestedScreenRoutes.root,
        onGenerateRoute: (routeSettings) {
          return MaterialPageRoute(
              builder: (context) => routeBuilders[routeSettings.name](context));
        },
      ),
    );
  }
   ...
}
  • In our newly introduced Navigator widget we will pass the navigatorKey as the key. This is how our widget tree looked before adding the Navigator widget:

Nested_Nav_Widget_Tree_Before.png

  • And this is our new widget tree after adding the Navigator widget:

Nested_Nav_Widget_Tree.png

  • We can see a new Navigator widget in our NestedScreen class. Now. when we click on Page A or Page B we will notice that the Nested Screen replaces only the body of the current screen, which is what we wanted. Refer to the picture given below:

Nested_nav_gif_2.gif

Decoding

Let's understand how to go about the decoding process in this segment. After adding a new Navigator widget, we will now have two Navigator widgets in our widget tree: One above the BottomNavigatorBar (in the MaterialApp) and one below the BottomNavigatorBar. After this, when we write Navigator.of(context).push(), the nearest (above) Navigator widget is accessed and everything below it is replaced. So, when we access the Navigator above the BottomNavigatorBar, it replaces the BottomNavigatorBar, but when we access the Navigator below the BottomNavigatorBar, is holding cause any changes

But there can be cases when we would like to use the other Navigator widget, such as if we want to replace the entire current screen with a screen on clicking Page B and replace only a part of it on clicking Page A. To do this, we will have to access the Navigator when we click on `Page B.

Let's see how can we do this.

Use keys to access the desired Navigator

To access a desired Navigator, we will have to pass the respective context while performing operations. Based on whether Page A or Page B is clicked, we will get either the context from the navigatorKey variable or NavigatorKeys.navigatorKeyMain which is used at the start of the MaterialApp level. For this, we will change the _push() method of the NestedScreen class as shown below:

class NestedScreen extends StatelessWidget {
...
  void _push(BuildContext context, String name) {
    BuildContext _desiredContext;
    if (name == 'Page 1') {
      _desiredContext = navigatorKey.currentContext;
    } else {
      _desiredContext = NavigatorKeys.navigatorKeyMain.currentContext;
    }
    Navigator.of(_desiredContext).push(...);
  }
...
}

Now, if we click on Page A the lower Navigator widget will be accessed and only the body of the BottomNavigationBar will change. and by clicking on Page B, the above Navigator widget will be accessed and the entire current screen will change.

Nested_nav_gif_3.gif

And that is how we use the desired Navigator widget from the widget tree!

Conclusion

The main ingredient to nested navigation is a second Navigator widget. When we start with a MaterialApp, we will introduce a Navigator which we can use throughout the app as shown in this article. When we call the Navigator.method(), then the nearest Navigator widget (above this widget in widget tree) is used and we can use keys to access the desired Navigator. Here’s the final project link .

Thanks for reading.