Flutter Navigation doesn't pop Navigator stack on back press - flutter

I'm using Navigator for named routes to switch pages on screen with a BottomNavigationBar. On Page 3, I can navigate to Page 4 by NavigationKey.currentState.pushNamed(Page4Route).
On Page 4, I'm able to navigate back to Page 3 by calling NavigationKey.currentState.pop, but pressing the back button of the device closes the app instead. Any idea why the back button doesn't pop the current screen on the Navigation stack? How can I handle this better?
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: navigationKey,
initialRoute: Pages.home,
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case Pages.home:
builder = (BuildContext context) => _page1();
break;
case Pages.page1:
builder = (BuildContext context) => _page1();
break;
case Pages.page2:
builder = (BuildContext context) => _page2();
break;
case Pages.page3:
builder = (BuildContext context) => _page3();
break;
case Pages.page4:
builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
bottomNavigationBar: BottomNavigationBar(),
...
);
}
Demo
Minimal Repro
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Routing Demo',
home: HomePage(),
);
}
}
class Pages{
static const home = '/';
static const page1 = '/page1';
static const page2 = '/page2';
static const page3 = '/page3';
static const page4 = '/page4';
}
class HomePage extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<HomePage> {
final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();
var _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: navigationKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case Pages.home:
builder = (BuildContext context) => _page1();
break;
case Pages.page1:
builder = (BuildContext context) => _page1();
break;
case Pages.page2:
builder = (BuildContext context) => _page2();
break;
case Pages.page3:
builder = (BuildContext context) => _page3();
break;
case Pages.page4:
builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
// You can also return a PageRouteBuilder and
// define custom transitions between pages
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.mood),
label: 'Page 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.connect_without_contact),
label: 'Page 2',
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
label: 'Page 3',
),
],
currentIndex: _currentPage,
// selectedItemColor: Colors.amber[800],
onTap: (value) {
/// Update page if a different tab from the current was clicked
if (value != _currentPage)
setState(() {
_currentPage = value;
switch (value) {
case 0:
navigationKey.currentState!
.pushReplacementNamed(Pages.page1);
break;
case 1:
navigationKey.currentState!
.pushReplacementNamed(Pages.page2);
break;
case 2:
navigationKey.currentState!
.pushReplacementNamed(Pages.page3);
break;
default:
/// TODO Error 404 page
throw Exception('Invalid route: $value');
}
});
},
),
);
}
Widget _page1() {
return Scaffold(
body: Container(
color: Colors.lightBlueAccent,
child: Center(
child: Text('Page 1'),
),
),
);
}
Widget _page2() {
return Scaffold(
body: Container(
color: Colors.orangeAccent,
child: Center(
child: Text('Page2'),
),
),
);
}
Widget _page3() {
return Scaffold(
body: Container(
color: Colors.lightGreenAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 3'),
ElevatedButton(onPressed: (){
navigationKey.currentState!.pushNamed(Pages.page4);
}, child: Text('Page 4')),
],
),
),
),
);
}
}
class Page4Screen extends StatefulWidget {
Page4Screen({Key? key, required GlobalKey<NavigatorState> navigatorKey}) : _navigatorKey = navigatorKey, super(key: key);
final GlobalKey<NavigatorState> _navigatorKey;
#override
createState() => Page4State();
}
class Page4State extends State<Page4Screen>{
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(AppLocalizations.of(context)!.txtTitleMood),
// automaticallyImplyLeading: false,
// ),
body: Container(
color: Colors.redAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 4'),ElevatedButton(
// Within the SecondScreen widget
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack.
widget._navigatorKey.currentState!.pop();
},
child: Text('Go Back!'),
),
],
),
),
),
);
}
}

The Navigator widget does not handle back buttons by default and that's your job to do it if you have defined a Navigator widget. You can catch back press by WillPopScope widget. It takes a Future<bool> Function() which will be called whenever user wants to go back. If it returns false then your default Navigator which lies in MaterialApp will not pop the current route which simply is showing your HomePage in this case. So if your nested navigator has something to pop (like Page4) then it will pop that and will prevent your main Navigator from popping your HomePage.
class _HomeState extends State<HomePage> {
...
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => !(await navigationKey.currentState!.maybePop()),
child: Scaffold(
...
),
);
}
}

Related

Handle Android system back button in Flutter app with nested navigators

I have a Flutter app composed of "sub-apps" with different theme colors.
In a sub-app, the AppBar's back button is working as expected: it navigates back to the previous sub-app page.
However the Android system's back button is NOT working as expected: it navigates directly to the root app.
The expected back navigation in the following example should be: B3 B2 B1 A3 A2 A1. But it's B3 A3 A2 A1.
In other words, I want the Android back button to work the same way as the Flutter back button.
The same problem happens with iOS "back swipe" gesture (iosPageTransition = true).
Please try the following code on an Android device or emulator and test with the system's back button.
Note that I use multiple MaterialApps to apply a color theme to all screens of a sub-app.
Also note that WillPopScope doesn't work since it's not triggered by the system's back button.
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
_ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
_AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
_AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _AppB(_PageB(1));
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
leading: number == 1 ? BackButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop()) : null,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
return scaffold;
}
}
First of all you can't set two MaterialApp in single flutter app its wrong way to use it, in your code it initialize 2 times.Just change your theme from the page you want to update,
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final iosPageTransition = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _PageA(1),
);
}
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.red),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _PageB(1);
return _PageA(number + 1);
}));
},
),
),
);
}
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () => Navigator.pop(context)) ,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
}
),
);
}
}
As Dharini said you cannot use 2 material apps, but if you really want to, then here is the workaround for you
Issue: When you try to B(n) it pops the B as it is new material app.
|
|_ A
|_ A1
|_ A2
|_ B
|_ B1
|_B2
Resolution: Pass the BuildContext and Route from B to A and remove route on back tap of icon or android back key.
Navigator.removeRoute(context, route);
Workaround
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
const _ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
const _AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
const _AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
_PageA(this.number);
final bRoutes = <BuildContext, Route>{};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) {
return WillPopScope(
onWillPop: () async {
if (number > 2 && bRoutes.isNotEmpty) {
Navigator.removeRoute(bRoutes.entries.last.key, bRoutes.entries.last.value);
bRoutes.remove(bRoutes.entries.last.key);
return false;
} else {
return true;
}
},
child: _AppB(_PageB(1, bRoutes)),
);
}
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
final Map<BuildContext, Route> bRoutes;
const _PageB(this.number, this.bRoutes);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
leading: BackButton(onPressed: () {
if (bRoutes.isNotEmpty) {
final context = bRoutes.entries.last.key;
final route = bRoutes.entries.last.value;
bRoutes.remove(bRoutes.entries.last.key);
Navigator.removeRoute(context, route);
}
})
,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
final route = MaterialPageRoute(builder: (context) {
return _PageB(number + 1, bRoutes);
});
bRoutes[context] = route;
Navigator.push(context, route);
},
),
),
);
}
}

how to keep bottom navigation bar in all pages with stateful widget in Flutter

I am able to navigate to multiple different pages with visible bottom navigation bar on all pages but not able to switch between all of them so how can I switch between tabs with bottom bar being there in all pages
I got till here using this Answer but not able to make it work i.e to switch between bottom navigation tabs...
in short I want to add view for my message tab i.e second tab and move to it also without losing my bottom navigation bar for every page i navigate to...
so far my code,
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomePage()));
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')));
}
Try like this:
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int activeIndex = 0;
void changeActivePage(int index) {
setState(() {
activeIndex = index;
});
}
List<Widget> pages = [];
#override
void initState() {
pages = [
Page1(() => changeActivePage(2)),
Page2(),
Page3(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: SizedBox(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(onPressed: () => changeActivePage(0), icon: Icon(Icons.call)),
IconButton(onPressed: () => changeActivePage(1), icon: Icon(Icons.message)),
],
),
),
body: pages[activeIndex]);
}
}
// 1st Page:
class Page1 extends StatelessWidget {
final Function callback;
const Page1(this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page1')),
body: Center(
child: RaisedButton(
onPressed: () => callback(),
child: Text('Go to Page3'),
),
),
);
}
}
// 2nd Page:
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) =>
Scaffold(appBar: AppBar(title: Text('Page2')));
}
// 3rd Page:
class Page3 extends StatelessWidget {
const Page3();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page3')),
body: Center(child: Text('Page3')),
);
}
}

Get current route name of CupertinoTabView in Flutter?

I'm using CupertinoTabScaffold and CupertinoTabView to build navigation bottom bar in my App. For one CupertinoTabView I go to others pushed routes name, I would like to get the current name of a CupertinoTabView, but I get Null
I define the routes in main like that
CupertinoApp(
home: MyApp(),
title: 'machin',
routes: appRoutes,)
final appRoutes = {
'/pushedName': (context) => PushedName(),
};
MyApp class //
final GlobalKey<NavigatorState> profileTabNavKey =
GlobalKey<NavigatorState>();
CupertinoTabScaffold(
tabBar: CupertinoTabBar(
activeColor: Color(0xff077018),
border: Border.all(color: Color(0xffffffff)),
currentIndex: widget.currentIndex,
onTap: (index) {},
items: <BottomNavigationBarItem>[....],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: profileTabNavKey,
routes: appRoutes,
builder: (BuildContext context) =>
SettingsView());
break;
default:
return HomePage();
}
},
),
In the SettingsView I pushed a named route by using
Navigator.pushNamed(context, '/pushedName')
I tried to get the route name in the my app class by using
print(ModalRoute.of(profileTabNavKey.currentContext).settings.name);
nb: in the pushedName View i get it perfectly any help , thanks in advance
Just use the BuildContext from the build widget to get the ModalRoute data :
ModalRoute.of(context).settings.name
Working example :
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
theme: CupertinoTheme.of(context).copyWith(
brightness: Brightness.light,
),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Home',
icon: Icon(CupertinoIcons.home),
),
BottomNavigationBarItem(
label: 'Setting',
icon: Icon(CupertinoIcons.settings),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 1:
return CupertinoTabView(
routes: <String, WidgetBuilder>{
'/setting': (context) => SettingsPage(),
'/setting/2': (context) => SettingsPage(2),
'/setting/2/3': (context) => SettingsPage(3),
},
builder: (context) => SettingsPage(),
);
break;
default:
return Center(
child: Text('Home page'),
);
}
},
);
}
}
class SettingsPage extends StatelessWidget {
final int index;
SettingsPage([this.index = 1]);
#override
Widget build(BuildContext context) {
// here we go to get the current route name
print(ModalRoute.of(context).settings.name);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
middle: Text(index > 1 ? 'Settings page - $index' : 'Settings page'),
),
child: Center(
child: CupertinoButton.filled(
child: Text('Go'),
onPressed: () {
if (index == 1) {
Navigator.pushNamed(context, '/setting/2');
} else if (index == 2) {
Navigator.pushNamed(context, '/setting/2/3');
}
},
),
),
);
}
}
Go to Dartpad
I found a solution that doesn't sound like best practice, but it works!
Instead of using ModalRouter and other libraries like navigation_history_observer, I used Navigator.popUntil and blocked the popup from getting the current route from the argument and assigned it to a variable.
WillPopScope(
onWillPop: () async {
String currentRoute;
navigatorKeys[_tabController.index].currentState.popUntil((route) {
currentRoute = route.settings.name;
return true;
});
if (currentRoute == '/') {
return Future.value(false);
} else {
return !await navigatorKeys[_tabController.index]
.currentState
.maybePop();
}
},
// ...
);

Flutter navigation

Can someone explain why not printing efeioi when it is back from pageE?
Page A
Navigator.pushNamed(context, PageB.ROUTE).then((onValue) {
print("efeioi");
});
Page B
Navigator.of(context)
.pushReplacementNamed(PageC.ROUTE, arguments: onValue);
PageC
Navigator.pushNamed(context, PageD.ROUTE,
arguments: onValue);
PageD
Navigator.pop(context); // back to Page C
Page C
Navigator.pushNamed(context, PageE.ROUTE,
arguments: onValue);
Page E
Navigator.of(context).popUntil(ModalRoute.withName(PageA.ROUTE));
I can't use Navigator.pop in Page E because it will back to Page C!
I have uploaded full code here
https://github.com/tony123S/navigation
As per your requirement I have implemented as below
main.dart
initState : this will be called when you navigate from E to A
refreshPage : it will not called as you already popped before returning to A Page
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: A(),
routes: <String, WidgetBuilder>{
'/A': (BuildContext context) => new A(),
'/B': (BuildContext context) => new B(),
'/C': (BuildContext context) => new C(),
'/D': (BuildContext context) => new D(),
'/E': (BuildContext context) => new E(),
},
);
}
}
class A extends StatefulWidget {
#override
_FirstRouteState createState() => _FirstRouteState();
}
class _FirstRouteState extends State<A> {
final String fromPage;
_FirstRouteState({Key key, #required this.fromPage});
#override
void initState() {
// TODO: implement initState
super.initState();
print("Called askdfjaksdfj");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page A'),
),
body: Center(
child: RaisedButton(
child: Text('Open B'),
onPressed: () {
// Navigate to second route when tapped.
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => B()),
// );
Navigator.push(
context,
MaterialPageRoute(builder: (context) => B()),
).then((res) => refreshPage());
},
),
),
);
}
refreshPage() {
print("refresh page is called");
}
}
class B extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("B Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.of(context).pushNamed(
"/C",
);
},
child: Text('Go to C'),
),
),
);
}
}
class C extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("C Page"),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pushNamed(
context,
"/D",
);
},
child: Text('Go to D'),
),
RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pushNamed(
context,
"/E",
);
},
child: Text('Go to E'),
),
],
),
),
);
}
}
class D extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("D Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pop(context);
},
child: Text('Go back to C'),
),
),
);
}
}
class E extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("E Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigator.pop(context);
// Navigator.of(context).pushNamed("/A");
// Navigator.of(context).popUntil(ModalRoute.withName('/A'));
Navigator.of(context)
.pushNamedAndRemoveUntil('/A', (Route<dynamic> route) => false,);
},
child: Text('Go to A'),
),
),
);
}
}
Please run code for better understanding and reply if you found any difficulty

How to do nested navigation in Flutter

Does anyone have any recommendations for figuring out nested navigation in Flutter?
What I want is to keep a persistent BottomNavigationBar even when redirecting to new screens. Similar to YouTube, where the bottom bar is always there, even when you browse deeper into the menus.
I'm unable to figure it out from the docs.
The only tutorial I have been able to find so far that goes in-depth into exactly my requirement is https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf (source code). However, It's super confusing.
Right now I'm using
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return Container()
However, its just pushing the new widget over the entire stack, covoring the BottomNavigationBar.
Any tips would be greatly appreciated!
Here is a simple example that even supports popping to the first screen with a tab bar.
import 'package:flutter/material.dart';
import '../library/screen.dart';
import '../playlists/screen.dart';
import '../search/screen.dart';
import '../settings/screen.dart';
class TabsScreen extends StatefulWidget {
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
int _currentIndex = 0;
final _libraryScreen = GlobalKey<NavigatorState>();
final _playlistScreen = GlobalKey<NavigatorState>();
final _searchScreen = GlobalKey<NavigatorState>();
final _settingsScreen = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: <Widget>[
Navigator(
key: _libraryScreen,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => LibraryScreen(),
),
),
Navigator(
key: _playlistScreen,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => PlaylistsScreen(),
),
),
Navigator(
key: _searchScreen,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => SearchScreen(),
),
),
Navigator(
key: _settingsScreen,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => SettingsScreen(),
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: (val) => _onTap(val, context),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.library_books),
title: Text('Library'),
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('Playlists'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
],
),
);
}
void _onTap(int val, BuildContext context) {
if (_currentIndex == val) {
switch (val) {
case 0:
_libraryScreen.currentState.popUntil((route) => route.isFirst);
break;
case 1:
_playlistScreen.currentState.popUntil((route) => route.isFirst);
break;
case 2:
_searchScreen.currentState.popUntil((route) => route.isFirst);
break;
case 3:
_settingsScreen.currentState.popUntil((route) => route.isFirst);
break;
default:
}
} else {
if (mounted) {
setState(() {
_currentIndex = val;
});
}
}
}
}
Here is the example code for persistent BottomNavigationBar as a starter:
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatelessWidget {
final navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Navigator(
key: navigatorKey,
onGenerateRoute: (route) => MaterialPageRoute(
settings: route,
builder: (context) => PageOne(),
),
),
),
BottomNavigationBar(navigatorKey)
],
),
);
}
}
class BottomNavigationBar extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
BottomNavigationBar(this.navigatorKey) : assert(navigatorKey != null);
Future<void> push(Route route) {
return navigatorKey.currentState.push(route);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: ButtonBar(
alignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text("PageOne"),
onPressed: () {
push(MaterialPageRoute(builder: (context) => PageOne()));
},
),
RaisedButton(
child: Text("PageTwo"),
onPressed: () {
push(MaterialPageRoute(builder: (context) => PageTwo()));
},
)
],
),
);
}
}
class PageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Page One"),
RaisedButton(
onPressed: (){
Navigator.of(context).pop();
},
child: Text("Pop"),
),
],
),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Page Two"),
RaisedButton(
onPressed: (){
Navigator.of(context).pop();
},
child: Text("Pop"),
),
],
),
);
}
}
Here is how it the screen record