Reputation: 542
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!
Upvotes: 20
Views: 23574
Reputation: 1975
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;
});
}
}
}
}
Upvotes: 32
Reputation: 10865
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
Upvotes: 7