Flutter Bottom Navigation Bar Tutorial Complete

Hi! This tutorial will guide you through creating a fully functional Bottom Navigation Bar in your Flutter application, complete with code snippets and explanations.

Why Bottom Navigation Bar?

In the landscape of mobile applications, the ease of navigation is a crucial element in defining the user experience. A Bottom Navigation Bar is a set of navigation buttons anchored at the bottom of the screen, making it accessible for users to navigate with thumbs while holding their devices. It’s optimal for apps with 3 to 5 top-level destinations.

Prerequisites

Ensure you have:

  • Flutter installed on your machine
  • Basic knowledge of Flutter & Dart
  • An IDE (Android Studio/VS Code)

Step 1: Setting Up Your Flutter Project

Begin by creating a new Flutter project. Open your terminal or command prompt and navigate to your desired directory. Execute the command:

flutter create bottom_nav_bar

Navigate into your project directory, and you will find the main entry point of your app in lib/main.dart.

Step 2: Building the Scaffold

Open lib/main.dart and start with a simple Scaffold. The Scaffold widget provides a framework that includes an AppBar, a Body, and a BottomNavigationBar.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Bottom Navigation Bar',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0; // new

  void _onItemTapped(int index) { // new
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Tutorial'),
      ),
      body: Center( // new
        child: Text('Tab $_selectedIndex content displays here'),
      ),
      bottomNavigationBar: BottomNavigationBar( // new
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: 'School',
          ),
        ],
        currentIndex: _selectedIndex, // new
        selectedItemColor: Colors.amber[800], // new
        onTap: _onItemTapped, // new
      ),
    );
  }
}

In this code, we’ve set up a Scaffold with a BottomNavigationBar. The _selectedIndex is managed by the state of _MyHomePageState, which changes when a different tab is tapped, thanks to the _onItemTapped function.

Step 3: Adding Navigation Logic

Now, let’s define the content for each tab and manage the navigation logic.

// Define a widget for each page
class HomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Home Page'));
  }
}

class BusinessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Business Page'));
  }
}

class SchoolWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('School Page'));
  }
}

// Update the _MyHomePageState class
class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static List<Widget> _widgetOptions = <Widget>[
    HomeWidget(),
    BusinessWidget(),
    SchoolWidget(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Tutorial'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: 'School',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

In this code, we’ve created three stateless widgets, one for each page corresponding to the Bottom Navigation Bar items. We also have a _widgetOptions list that holds the widgets and is used to display the current selection.

Step 4: Customizing the Bottom Navigation Bar

Next, we’ll customize the appearance of the Bottom Navigation Bar to fit the app’s design language.

// Customization options for BottomNavigationBar
bottomNavigationBar: BottomNavigationBar(
  items: const <BottomNavigationBarItem>[
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: 'Home',
      backgroundColor: Colors.red,
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.business),
      label: 'Business',
      backgroundColor: Colors.green,
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.school),
      label: 'School',
      backgroundColor: Colors.purple,
    ),
  ],
  currentIndex: _selectedIndex,
  selectedItemColor: Colors.white,
  unselectedItemColor: Colors.grey,
  onTap: _onItemTapped,
  type: BottomNavigationBarType.shifting, // new
),

In the above snippet, type: BottomNavigationBarType.shifting is added to provide a shifting animation when changing tabs, and backgroundColor is set for each item.

Step 5: Handling Page State

When switching between tabs, it’s crucial to maintain the state of each page. Flutter’s PageView combined with IndexedStack is a great way to maintain the state of each page without rebuilding them from scratch every time a tab is tapped.

// Update the _MyHomePageState class to use IndexedStack
class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  final PageController _pageController = PageController();

  void _onPageChanged(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  void _onItemTapped(int index) {
    _pageController.jumpToPage(index);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Tutorial'),
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: _onPageChanged,
        children: _widgetOptions,
        physics: NeverScrollableScrollPhysics(), // Disable swipe to change tabs
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          // BottomNavigationBarItems...
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

Here, we’ve introduced a PageController to manage which page is visible in the body of the Scaffold. We also replaced the Center widget with a PageView that’s controlled by the PageController. This allows us to preserve the state of our pages.

Step 6: Adding Transitions and Animations

To enhance the user experience, we can add transitions and animations when switching between tabs. Flutter makes it easy to create these animations with its built-in widgets and properties.

// Update the _onItemTapped function to animate page transitions
void _onItemTapped(int index) {
  _pageController.animateToPage(
    index,
    duration: Duration(milliseconds: 500), // Adjust the speed of the animation
    curve: Curves.easeInOut, // Choose the type of animation curve
  );
}

In this updated _onItemTapped method, we’re using _pageController.animateToPage instead of jumpToPage, which lets us specify the duration and curve for the page transition animation.

Step 7: Dynamic Theming

Flutter’s flexibility allows us to dynamically change the theme of the Bottom Navigation Bar based on the active tab or user preferences.

// Example of dynamic theming based on the selected index
BottomNavigationBar(
  // ...
  backgroundColor: _selectedIndex == 0 ? Colors.red : _selectedIndex == 1 ? Colors.green : Colors.blue,
  // ...
),

In this example, we change the backgroundColor of the Bottom Navigation Bar dynamically, based on the _selectedIndex.

Step 8: Adaptive Layouts

As we are developing for multiple platforms and screen sizes, it’s important to make our Bottom Navigation Bar adaptive.

// Use MediaQuery to adapt the layout based on the screen size
Widget build(BuildContext context) {
  var isWideScreen = MediaQuery.of(context).size.width > 600; // arbitrary breakpoint for wide screens

  return Scaffold(
    // ...
    bottomNavigationBar: isWideScreen ? null : BottomNavigationBar(
      // ...
    ),
    drawer: isWideScreen ? Drawer(
      // Add a drawer with navigation items for wide screens
      child: ListView(
        // Drawer items...
      ),
    ) : null,
    // ...
  );
}

This code uses MediaQuery to check the screen width and decide whether to show a BottomNavigationBar or a Drawer, making it responsive to different screen sizes.

Conclusion

With these steps, you now have a robust and feature-rich Bottom Navigation Bar in your Flutter application. The code examples provided offer a comprehensive understanding of how to implement, manage, and enhance this component.

Remember, the key to a successful implementation is not only the initial setup but also considering user interaction, maintaining state, and ensuring your UI adapts to different devices and orientations.

For more advanced topics, such as incorporating nested navigation or adding badges to navigation bar items, check out the official Flutter documentation.

Happy coding, and may your Flutter development journey be as smooth as your app’s new navigation!

Hussain Humdani

Hussain Humdani

while ( ! ( succeed = try() ) );