Flutter: Keep BottomNavigationBar When Push to New Screen with Navigator
Screenshot:
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));
},
),
),
);
}
}