Post navigation

Flutter

Flutter bottom navigation bar example

After searching google for examples of Flutter navigation, more specifically the bottom navigation bar I was a bit stumped. Several of the examples I found would show the bottom navigation bar on one page but not persist it across all pages, and that wasn’t what I was after. Eventually I stumbled upon this excellent article by Andrea Bizzotto not only showing me how to persist the bottom navigation bar on every page, but also how to combine this with a stack navigator on each page – fantastic!

After working through his bottom navigation example and getting it working for me locally, I started to try and work out how to adapt this to build an app I had in mind (hopefully I’ll be posting about the launch of that soon!). It would need a bottom navigation bar, a stack navigator on at least one of those pages, but then a single page on at least one of the others. So I adapted Andrea’s code to do just that and you can find it here on github.

If you want to understand the changes I had to make then here are the code changes step by step – I started by forking his repo but found it wouldn’t run and I had to add this to pubspec.yaml:

environment:  sdk: ">=2.7.0 <3.0.0"

In order to update this code to handle some pages with a stack and some as a standalone page I made the following changes.

Combine a tab navigator with the bottom navigation bar

First of all a new property needed to be added to the TabNavigator class in tab_navigator.dart, so that as well as the navigatorKey and the tabItem properties, there’s a new one called stacked, which is a boolean. So the top of the TabNavigator class now looked like this:

class TabNavigator extends StatelessWidget {
  TabNavigator({this.navigatorKey, this.tabItem, this.stacked});
  final GlobalKey<NavigatorState> navigatorKey;
  final TabItem tabItem;
  final bool stacked;

  void _push(BuildContext context, {int materialIndex: 500}) {
    var routeBuilders = _routeBuilders(context, materialIndex: materialIndex);
    ....
    ....

The build method in tab_navigator.dart also needed to be updated so that if a page is a ‘stacked’ page, the routeBuilders method is still called, but if not, the SinglePage is returned.

@override
Widget build(BuildContext context) {
    final routeBuilders = _routeBuilders(context);
    return Navigator(
        key: navigatorKey,
        initialRoute: TabNavigatorRoutes.root,
        onGenerateRoute: (routeSettings) {
          if (stacked) {
            return MaterialPageRoute(
              builder: (context) {
                return routeBuilders[routeSettings.name](context);
              },
            );
          }

          return MaterialPageRoute(
            builder: (context) => SinglePage(
              color: activeTabColor[tabItem],
              title: tabName[tabItem],
              materialIndex: 0,
            ),
          );
        });
  }

Add new file single_page.dart containing this code:

import 'package:flutter/material.dart';

class SinglePage extends StatelessWidget {
  SinglePage({this.color, this.title, this.materialIndex: 500});
  final MaterialColor color;
  final String title;
  final int materialIndex;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: color,
        title: Text(
          '$title[$materialIndex]',
        ),
      ),
      body: Container(
        color: color[materialIndex],
        child: Center(
            child: Text(
          "I am a single page.",
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 28),
        )),
      ),
    );
  }
}

Finally add a reference to that page in tab_navigator.dart:

import 'package:nested_navigation_demo_flutter/single_page.dart';

Next I needed to modify app.dart, to identify which page would be a single page, and which would be stacked.

Add new property to the _buildOffstageNavigator method, so that we can pass in whether each page has a stack or not.

Widget _buildOffstageNavigator(TabItem tabItem, bool isStacked) {
    return Offstage(
      offstage: _currentTab != tabItem,
      child: TabNavigator(
        navigatorKey: _navigatorKeys[tabItem],
        tabItem: tabItem,
        stacked: isStacked,
      ),
    );
  }

Once that change had been made I had to change the code where the _buildOffstageNavigator method is called to include the new boolean property. This is in the build method of Appstate.dart

....
child: Scaffold(
        body: Stack(children: <Widget>[
          _buildOffstageNavigator(TabItem.red, true),
          _buildOffstageNavigator(TabItem.green, false),
          _buildOffstageNavigator(TabItem.blue, true),
        ]),
        bottomNavigationBar: BottomNavigation(
          currentTab: _currentTab,
          onSelectTab: _selectTab,
        ),
      ),
        
        .......

Once all of those changes are made you should have an app that works like you can see in this video, with a Flutter bottom navigation bar on every page, with two of the main pages showing stack navigation and one just a single page.

So that’s a fully working Flutter bottom navigation bar example. You may also be interested in a blog post I wrote recently about my experience installing and setting up Flutter on my PC, be sure to read it if you are just starting out.