What is bottom_nav_layout
?
It is a quick flutter app layout for building an app with a bottom nav bar. You can get an app with fluent behavior running in 15 lines of code.
Why bottom_nav_layout
?
- Eliminates all boilerplate code for bottom nav bar coordination.
- Uses identical APIs with the underlying bottom bars.
- Offers additional common features, all of which are optional.
- Page state preservation
- Lazy page loading
- Page backstack
- Back button navigation management
- Works with any bottom bar you wish. Use the material desing, grab one from pub.dev or use your own.
Content
- Usage
- Page State Preservation
- Lazy Page Loading
- Page Back Stack
- In-Page Navigation Using GlobalKeys
- Different Bottom Bars
- Bar Styling
- Improvements
- Community
Usage
Installation
Add the following to your pubspec.yaml
file.
dependencies:
bottom_nav_layout: latest_version
Import
import 'package:bottom_nav_layout/bottom_nav_layout.dart';
Quick Start Example
void main() => runApp(MaterialApp(
home: BottomNavLayout(
// The app's top level destinations
pages: [
Center(child: Text("Welcome to bottom_nav_layout")),
SliderPage(),
Center(child: TextField(decoration: InputDecoration(hintText: 'Search...'))),
],
// Delegates its properties to a BottomNavigationBar.
navBarDelegate: BottomNavigationBarDelegate(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.linear_scale), label: 'Slider'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
],
),
),
));
Done. You have a complete, working application.
SliderPage code
class SliderPage extends StatefulWidget {
@override
_SliderPageState createState() => _SliderPageState();
}
class _SliderPageState extends State<SliderPage> {
double r = 0;
@override
Widget build(BuildContext context) => Center(
child: Slider(
value: r,
onChanged: (double d) => setState(() => r = d),
),
);
}
Parameters
Name | Description |
---|---|
pages |
Top level destinations of your application. |
pageBuilders |
Also top level destinations but can be lazily loaded. |
savePageState |
Flag to enable/disable saving page state. |
pageStack |
Navigation stack that remembers pages visited. Enhances back button management on Android. |
keys |
Keys that help the layout manage in-page navigation. |
bottomBarWrapper |
Widget that wrap bottom bar. |
extendBody |
Passed to Scaffold.extendBody . |
resizeToAvoidBottomInset |
Passed to Scaffold.resizeToAvoidBottomInset . |
navBarDelegate |
Properties passed into it such as items , onTap , elevation , etc. are used to construct the underlying bottom bar. |
Inner Widget Tree
Page State Preservation
The state changes you made in a page such as scroll amount, sub-navigation, form inputs etc. are preserved. You can enable it as per Cupertino Design Guidelines or disable it as per Material Design Guidelines
savePageState: true, // Default is true
Lazy Page Loading
Instead of passing pages
, pass pageBuilders
.
pageBuilders
are simple Functions that immediately return the corresponding page. When used, the pages are not created until they are navigated to for the first time. This is useful when a non-initial page has a load animation or runs an unnecessary heavy process.
pageBuilders: [
() => Center(child: Text("Welcome to bottom_nav_layout")),
() => GamePage('TicTacToe'),
() => Center(child: TextField(decoration: InputDecoration(hintText: 'Search...'))),
],
If savePageState
is set to false, pages
and pageBuilders
do the same thing.
Page Back Stack
The layout remembers the order of pages navigated and when back button is pressed, navigates back to the previously navigated page. There are different ways of organizing a page back stack, many of which are readily implemented. You can also implement your own.
pageStack: ReorderToFrontPageStack(initialPage: 0), // Default
// pageStack: StandardPageStack(initialPage: 0),
// pageStack: ReorderToFrontExceptFirstPageStack(initialPage: 0),
// pageStack: NoPageStack(initialPage: 0),
// pageStack: FirstAndLastPageStack(initialPage: 0),
Page Back Stack Types
Consider the following use case. After launching the app, the user;
- Start at page 0
- Navigate to page 1
- Navigate to page 2
- Navigate to page 1
- Press back button
- Navigate to page 0
- Press back button
Let's look at how different PageStacks behave in this scenario.
StandardPageStack
This behavior is used by Google Play app.
Event | Initial | push(1) | push(2) | push(1) | pop() | push(0) | pop() |
---|---|---|---|---|---|---|---|
Stack | 0 | 0->1 | 0->1->2 | 0->1->2->1 | 0->1->2 | 0->1->2->0 | 0->1->2 |
ReorderToFrontPageStack
This is the default behavior. This behavior is used by Instagram, Reddit, and Netflix apps.
Event | Initial | push(1) | push(2) | push(1) | pop() | push(0) | pop() |
---|---|---|---|---|---|---|---|
Stack | 0 | 0->1 | 0->1->2 | 0->2->1 | 0->2 | 2->0 | 2 |
ReorderToFrontExceptFirstPageStack
This behavior is used by Youtube app.
Event | Initial | push(1) | push(2) | push(1) | pop() | push(0) | pop() |
---|---|---|---|---|---|---|---|
Stack | 0 | 0->1 | 0->1->2 | 0->2->1 | 0->2 | 0->2->0 | 0->2 |
NoPageStack
This behavior is the same as the behavior in BottomNavigationBar
example given in flutter docs. It is used by a lot of applications. It is also both Cupertino's and Material's default behavior.
Event | Initial | push(1) | push(2) | push(1) | pop() | push(0) | pop() |
---|---|---|---|---|---|---|---|
Stack | 0 | 1 | 2 | 1 | Exit App | N/A | N/A |
FirstAndLastPageStack
This behavior is used by Google, Gmail, Facebook, and Twitter apps.
Event | Initial | push(1) | push(2) | push(1) | pop() | push(0) | pop() |
---|---|---|---|---|---|---|---|
Stack | 0 | 0->1 | 0->2 | 0->1 | 0 | 0 | Exit App |
In-Page Navigation Using GlobalKeys
Figure: Flat Navigation
- Allows the layout to manage a flat navigation pattern.
- Let's us go back to the root route, when the bottom bar item on the current index is selected again.
To do this, the page should have a Navigator
widget and the same instance of the key should be used as the Navigator
's key in the corresponding page.
Example code to be added here...
To use keys, pass all the keys you passed to the pages in the same order.
keys: <GlobalKey<NavigatorState>?>[
homePageKey,
null, // If a page doesn't use a key, pass null so that layout knows the order
placePageKey,
],
Different Bottom Bars
So far, we only worked on Material design bottom nav bar. The layout also supports other bar designs. To use the design you want, pass the corresponding navBarDelegate
to the layout.
The navBarDelegate
's APIs are all identical with the respective packages. You will need to import the corresponding bottom bar package to be able to pass some of the parameters. Make sure to check out their documentation before using.
Warning: Some of the packages' index constructor parameter acts as an initialIndex
, not as a currentIndex
, therefore, selected item cannot be changed when the back button is pressed. To have the best result, only use NoPageStack
with bottom bars that doesn't have the currentIndex
property.
1. Material
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
BottomNavigationBar | BottomNavigationBarDelegate |
Yes |
2. convex_bottom_bar
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
convex_bottom_bar | ConvexAppBarDelegate |
No |
Example:
navBarDelegate: ConvexAppBarDelegate(
items: [
TabItem(icon: Icon(Icons.home), title: 'Home'),
TabItem(icon: Icon(Icons.linear_scale), title: 'Slider'),
TabItem(icon: Icon(Icons.search), title: 'Search'),
],
),
3. flutter_snake_navigationbar
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
flutter_snake_navigationbar | SnakeNavigationBarDelegate |
Yes |
Example:
navBarDelegate: SnakeNavigationBarDelegate(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.linear_scale), label: 'Slider'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
],
height: 56,
),
4. salomon_bottom_bar
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
salomon_bottom_bar | SalomonBottomBarDelegate |
Yes |
Example:
navBarDelegate: SalomonBottomBarDelegate(
items: [
SalomonBottomBarItem(icon: Icon(Icons.home), title: Text('Home')),
SalomonBottomBarItem(icon: Icon(Icons.linear_scale), title: Text('Slider')),
SalomonBottomBarItem(icon: Icon(Icons.search), title: Text('Search')),
],
),
5. bottom_bar_with_sheet
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
bottom_bar_with_sheet | BottomBarWithSheetDelegate |
No |
Example:
navBarDelegate: BottomBarWithSheetDelegate(
items: [
BottomBarWithSheetItem(icon: Icons.home),
BottomBarWithSheetItem(icon: Icons.linear_scale),
BottomBarWithSheetItem(icon: Icons.linear_scale),
BottomBarWithSheetItem(icon: Icons.search),
],
sheetChild: Center(child: Text("Welcome to sheetChild")),
),
6. water_drop_nav_bar
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
water_drop_nav_bar | WaterDropNavBarDelegate |
Yes |
Example:
navBarDelegate: WaterDropNavBarDelegate(
barItems: [
BarItem(filledIcon: Icons.home_filled, outlinedIcon: Icons.home_outlined),
BarItem(filledIcon: Icons.linear_scale, outlinedIcon: Icons.linear_scale_outlined),
BarItem(filledIcon: Icons.search, outlinedIcon: Icons.search),
],
),
7. sliding_clipped_nav_bar
Documentation | NavBarDelegate | Has currentIndex |
---|---|---|
sliding_clipped_nav_bar | SlidingClippedNavBarDelegate |
Yes |
Example:
navBarDelegate: SlidingClippedNavBarDelegate(
barItems: [
BarItem(icon: Icons.home, title: 'Home'),
BarItem(icon: Icons.linear_scale, title: 'Slider'),
BarItem(icon: Icons.search, title: 'Search'),
],
activeColor: Colors.blue,
),
Other Bar Designs
You can use any bottom bar design from pub.dev (even if the package is not included here) or create your own. To do this:
- Extend
NavBarDelegate
class . - Pass an instance of it to
BottomNavLayout.navBarDelegate
.
Incompatible Packages:
- persistent_bottom_nav_bar: Already a layout package
- animated_bottom_navigation_bar: Uses a parent
Scaffold
's properties to render.
Bar Styling
Bar Wrapper
Do you not like how your bottom bar looks? You can style it by wrapping it inside any widget.
bottomBarWrapper: (bottomBar) => Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: bottomBar,
),
Extend Body
You can have the page extend behind the bottom bar.
extendBody: true,
Improvements
- I am planning to add more bottom bar designs, preferably from pub.dev.
- Tell me if you want to see a feature your app has/needs in this package. I will do my best to integrate that.
- I am also considering to make a drawer_nav_layout package. If you are interested, let me know!
Community
Any feedback is appreciated. 🚀🚀
If you have queries, feel free to create an issue.
If you would like to contribute, feel free to create a PR.