Flutter: Keep BottomNavigationBar When Push to New Screen with Navigator
Solution 1
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
.
Solution 2
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')));
}
Solution 3
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
Solution 4
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));
},
),
),
);
}
}
Solution 5
Another way to achieve this (though not good practice) is to nest a material app in the body of your scaffold. And handle all "sub-navigation" there.
So, your hierarchy will look like this
Material App
- home
- Scaffold
- body
- Material App
- Scaffold
- AppBar
- body
...
- routes (internal)
- bottomNavigationBar
- routes (external)
I've tried this and it works perfectly. Unfortunately I can't post the source code now.
harry
Updated on July 29, 2022Comments
-
harry almost 2 years
In iOS, we have a UITabBarController which stays permanently at the bottom of the screen when we push to a new ViewController.
In Flutter, we have a bottomNavigationBar of a Scaffold. However, unlike iOS, when we
Navigator.push
to a new screen, this bottomNavigationBar disappears.In my app, I want to fulfil this requirement: Home screen has a
bottomNavigationBar
with 2 items (a & b) presenting screen A & B. By default, screen A is displayed. Inside screen A, there is a button. Tap that button,Navigator.push
to screen C. Now in screen C, we can still see thebottomNavigationBar
. Tap item b, I go to screen B. Now in screen B, tap item a in thebottomNavigationBar
, I go back to screen C (not A, A is currently below C in the navigation hierarchy).How can I do this? Thanks, guys.
Edit: I'm including some pictures for demonstration:
Screen A Screen A
Tap Go to C button, push to screen C Screen C
Tap Right item inside bottom navigation bar, go to screen B Screen B
-
giorgio79 almost 4 yearsMaterial Design now recommends navigation in sub-pages, as per github.com/flutter/flutter/issues/16221 and material.io/components/bottom-navigation#behavior It is just not yet implemented...
-
ThomasThiebaud over 3 yearsWorks like a charm. Thanks a lot
-
Guilherme Ferreira over 3 yearsCould not find a generator for route RouteSettings
-
MwBakker about 3 yearsis it now implemented? I can still only find hacky workarounds or using 3rd party packages
-
Waqas Younis about 3 yearsAnd how to navigate some screens without bottom navigation bar, could you explain that too?
-
batangaming about 3 yearsYou can use root navigator:
Navigator.of(context, rootNavigator: true).pushFunction(...)
but you also need to configure routes in root, e.g setroutes
property in materialApp -
Lakshmi Narayanan almost 3 years@batangaming your comment just saved my day Thanks man
-
CopsOnRoad over 2 years@CNK Any references for "not recommended"?
-
KunYu Tsai over 2 yearsBut how to navigate between different tabs with this solution?
-
CopsOnRoad over 2 years@KunYuTsai Add the
onChanged
property to theBottomNavigationBar
widget, use an index to keep track of the current page and then use anif-else
condition, for example, to choose which widget to show. -
ladookie over 2 years@CopsOnRoad do you mean
onTap
? theonChanged
property doesn't exist onBottomNavigationBar
-
CopsOnRoad over 2 years@ladookie Yeah, it's
onTap
, I think it wasonChanged
in earlier Flutter versions. -
Mithson over 2 years@CopsOnRoad can you please provide a sample code for what kunYu Tsai had problem I am too facing the same issue but not able to make it work with Stateful widget
-
LearnFlutter about 2 yearsHi, I'm having the same issue, I have posted the question if you don't mind looking at it. @CopsOnRoad stackoverflow.com/questions/71484514/…
-
LearnFlutter about 2 yearsHaving the same issue on my case if you don't mind taking a look at it stackoverflow.com/questions/71484514/…
-
Krahmal about 2 yearsIs is possible to pop back from page2 to page1?