How to create a material 3 bottom navigation bar in Flutter

Share

Flutter is a UI development kit created by Google. It uses the Dart programming language to construct hybrid or cross-platform apps.

Material design is a flexible and adaptable methodology that helps create excellent digital experiences.

Bottom navigation is one of the components of material design. It allows easy navigation between main destinations in an app.

To create the material design of 3 types of bottom navigation bars in Flutter, we only need to use the NavigationBar class and track the changes of the currently selected tab on our navigation bar.

Note: We must be using Flutter version 3.0 or higher to follow this tutorial because this version gives us access to Material 3 widgets.

What is the NavigationBar?

The NavigationBar widget is used as the equivalent of the BottomNavigationBar widget, which conforms more to material design 2. With that being said, let’s see how to create this navigation bar.

First, create a new flutter project and replace everything in the main.dart with the code below.

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Buttom Navigation Bar',
home: Scaffold(
body: Center(
child: Text(
'Hello World πŸ‘‹',
),
),
),
);
}
}

Explanation

  • Line 4: We run the MyApp class in the runApp method, which is responsible for displaying MyApp widget on the screen.

  • Lines 7–13: In the MyApp class, we return the MaterialApp widget to give us access to many other widgets, which are created to make building our UIs easier.

  • Line 15: Under the Scaffold widget, we have a property called home which serves as the default route of the app when we open the app.

  • Lines 16–24: The Scaffold widget gives us access to quite a number of valuable properties such as app-bar, body, drawer, bottom navigation bar, and some other properties. We'll be using only two properties: the body and the bottom navigation bar property.

Also, we need to add the bottomNavigationBar property provided by the Scaffold widget and pass in the BottomNavigationBar widget or NavigationBar widget. For this example, we'll use the NavigationBar widget because it is best suited to achieve our goal.

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Buttom Navigation Bar',
home: Scaffold(
body: const Center(
child: Text(
'Hello World πŸ‘‹',
),
),
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) {
print('Selected $index');
},
selectedIndex: 0,
destinations: const <NavigationDestination>[
NavigationDestination(
selectedIcon: Icon(Icons.person),
icon: Icon(Icons.person_outline),
label: 'Learn',
),
NavigationDestination(
selectedIcon: Icon(Icons.engineering),
icon: Icon(Icons.engineering_outlined),
label: 'Relearn',
),
NavigationDestination(
selectedIcon: Icon(Icons.bookmark),
icon: Icon(Icons.bookmark_border),
label: 'Unlearn',
),
],
),
),
);
}
}

Explanation

  • Lines 21–43: We assign the NavigationBar widget to the bottomNavigationBar property.

The NavigationBar widget

The NavigationBar widget gives us access to a few widgets such as the following:

  • onDestinationSelected: The onDestinationSelected property takes a function that takes an int as an argument. The function is called when a user taps on a navigation bar item (NavigationDestination). The int argument represents the index of the selected item.

  • selectedIndex: The seletedIndex property determines which of the navigation bar items (NavigationDestination) is selected.

  • destinations: The list of widgets (NavigationDestination) that will be displayed in our NavigationBar widget is passed to the Navigation widget through the destinations property. The NavigationDestination requires us to pass an icon and label properties as a required property/parameter.

Displaying selected navigation item

We need to convert our MyApp class from a StatelessWidget to a StatefulWidget (because we need to rebuild some parts of the widget). To do this (either on VS Code or Android Studio), double-tap on the stateless keyword and click the "lightbulb" icon that appears on the left or right side of the editor. An option telling us to convert our widget to a StatefulWidget is shown.

Converting from a Stateless widget to a Stateful widget
Converting from a Stateless widget to a Stateful widget

Next, we want to create a new variable called selectedPageIndex and assign it an integer. Here we'll assign 0 as the starting value. Next, pass the selectedPageIndex to the selectedIndex property in the NavigationBar widget.

Then, we need to make the selectedPageIndex update based on the selected navigation bar item (NavigationDestination). We'll achieve this by assigning the selectedPageIndex to the index of the selected page.

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int selectedPageIndex = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Buttom Navigation Bar',
home: Scaffold(
body: const [
Center(
child: Text(
'Learn πŸ“—',
),
),
Center(
child: Text(
'Relearn πŸ‘¨β€πŸ«',
),
),
Center(
child: Text(
'Unlearn πŸ›',
),
),
],
bottomNavigationBar: NavigationBar(
selectedIndex: selectedPageIndex,
onDestinationSelected: (int index) {
setState(() {
selectedPageIndex = index;
});
},
destinations: const <NavigationDestination>[
NavigationDestination(
selectedIcon: Icon(Icons.person),
icon: Icon(Icons.person_outline),
label: 'Learn',
),
NavigationDestination(
selectedIcon: Icon(Icons.engineering),
icon: Icon(Icons.engineering_outlined),
label: 'Relearn',
),
NavigationDestination(
selectedIcon: Icon(Icons.bookmark),
icon: Icon(Icons.bookmark_border),
label: 'Unlearn',
),
],
),
),
);
}
}

Explanation

  • Line 15: We create a new variable to hold the selected page index.

  • Line 39: We pass the selectedPageIndex variable to the selectedIndex property.

  • Lines 40–44: The setState() method tells the Flutter engine that the internal state of an object has changed, so Flutter should check and then update the UI on the device accordingly.

To make the bottom navigation bar functional, we need to add the list of widgets needed to effectively switch between tabs, and pass the index of the selected tab. This is done so that flutter knows the index of the widget that is needed to be displayed when a button is tapped.

body: [
const Center(
child: Text(
'Learn πŸ“—',
),
),
const Center(
child: Text(
'Relearn πŸ‘¨β€πŸ«',
),
),
const Center(
child: Text(
'Unlearn πŸ›',
),
),
][selectedPageIndex],

Note: The widget that needs to be displayed must match the number of navigation items (NavigationDestination) created.

Full code

Our full working code is as follows:

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int selectedPageIndex = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Buttom Navigation Bar',
home: Scaffold(
body: const [
Center(
child: Text(
'Learn πŸ“—',
),
),
Center(
child: Text(
'Relearn πŸ‘¨β€πŸ«',
),
),
Center(
child: Text(
'Unlearn πŸ›',
),
),
][selectedPageIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: selectedPageIndex,
onDestinationSelected: (int index) {
setState(() {
selectedPageIndex = index;
});
},
destinations: const <NavigationDestination>[
NavigationDestination(
selectedIcon: Icon(Icons.person),
icon: Icon(Icons.person_outline),
label: 'Learn',
),
NavigationDestination(
selectedIcon: Icon(Icons.engineering),
icon: Icon(Icons.engineering_outlined),
label: 'Relearn',
),
NavigationDestination(
selectedIcon: Icon(Icons.bookmark),
icon: Icon(Icons.bookmark_border),
label: 'Unlearn',
),
],
),
),
);
}
}
A visual of our example application
A visual of our example application