Flutter: Keep BottomNavigationBar When Push to New Screen with Navigator

Screenshot:

enter image description here


Starting point:

void main() => runApp(MaterialApp(home: HomePage()));

HomePage [BottomNavigationBar + Page1]

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.orange,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.call), label: 'Call'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
        ],
      ),
      body: Navigator(
        onGenerateRoute: (settings) {
          Widget page = Page1();
          if (settings.name == 'page2') page = Page2();
          return MaterialPageRoute(builder: (_) => page);
        },
      ),
    );
  }
}

1st Page:

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page1')),
      body: Center(
        child: RaisedButton(
          onPressed: () => Navigator.pushNamed(context, 'page2'),
          child: Text('Go to Page2'),
        ),
      ),
    );
  }
}

2nd Page:

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Page2')));
}

You could actually place a placeholder inside body so the structure like this

- AppBar
- body (dynamic content from placeholder)
- BottomNavigationBar

Then you would have another class as a placeholder So each time you tap on the BottomNavigationBar it will refresh content of the body

One example I found is here https://willowtreeapps.com/ideas/how-to-use-flutter-to-build-an-app-with-bottom-navigation

and here but a litte too complex and not working for me https://medium.com/@swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5

and this https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf


tl;dr: Use CupertinoTabBar with CupertinoTabScaffold

The problem is not in Flutter but in UX just like Rémi Rousselet has mentioned.

It turned out Material Design doesn't recommend sub-pages in the hierarchy to access the Bottom navigation bar.

However, iOS Human Interface Guide recommend this. So, to use this feature, I had to adapt Cupertino widgets instead of Material ones. Specifically, in main, return a WidgetsApp/MaterialApp which contains a CupertinoTabScaffold. Implement the tab bar with a CupertinoTabBar and each screen is a CupertinoTabView.


Option 1: If you only want to keep BottomNavigationBar then try to use this.

Option 2: Use CupertinoTabBar as shown below for the static BottomNavigationBar.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqttdemo/Screen2.dart';
import 'package:mqttdemo/Screen3.dart';

import 'Screen1.dart';

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  int _currentIndex;
  List<Widget> _children;

  @override
  void initState() {
    _currentIndex = 0;
    _children = [
      Screen1(),
      Screen2(),
      Screen3(),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        currentIndex: _currentIndex,
        onTap: onTabTapped,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text("Screen 1"),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text("Screen 2"),
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.home), title: Text("Screen 3")),
        ],

      ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              return SafeArea(
                top: false,
                bottom: false,
                child: CupertinoApp(
                  home: CupertinoPageScaffold(
                    resizeToAvoidBottomInset: false,
                    child: _children[_currentIndex],
                  ),
                ),
              );
            },
          );
        }
    );
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }
}

Navigate to screen4 from Screen3 as shown below:

    class Screen3 extends StatefulWidget {
      @override
      _Screen3State createState() => _Screen3State();
    }
    
    class _Screen3State extends State<Screen3> {
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.black,
          child: Center(
            child: RaisedButton(
              child: Text("Click me"),
              onPressed: () {
                Navigator.of(context, rootNavigator: false).push(MaterialPageRoute(
                    builder: (context) => Screen4(), maintainState: false));
              },
            ),
          ),
        );
      }

}