DEV Community

Paul Halliday
Paul Halliday

Posted on • Originally published at developer.school on

Flutter Bottom Navigation Bar How to Create a BottomNavigationBar with Flutter

How to Create a BottomNavigationBar with Flutter

In this article we'll be creating a BottomNavigationBar with the ability to switch between different tabs using IndexedStack. You'll want to use this when creating tab based user interface(s) with their own navigation stack.

Our example application will be simple. It'll consist of four screens total, three of which are "main" tab pages, the final one being a detail page that we push on the navigation stack.

New Project

Let's create a new Flutter project in the terminal:

# New Flutter project
$ flutter create flutter_shopping

# Open in editor
$ cd flutter_tabs && code .
Enter fullscreen mode Exit fullscreen mode

Page Creation

We can start off by creating our main pages that will be displayed in our tab system. These will be simple StatelessWidgets and the content doesn't matter for our tutorial.

/// lib/presentation/shop/pages/shop_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_shopping/presentation/product_detail/pages/product_detail_page.dart';

class ShopPage extends StatelessWidget {
  static Route<dynamic> route() => MaterialPageRoute(
        builder: (context) => ShopPage(),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Shop"),
      ),
      body: Center(
        child: FlatButton(
          onPressed: () => Navigator.of(context).push(
            ProductDetailPage.route(),
          ),
          child: Text("Navigate to Product Detail Page"),
        ),
      ),
    );
  }
}


/// lib/presentation/home/pages/home_page.dart
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  static Route<dynamic> route() => MaterialPageRoute(
        builder: (context) => HomePage(),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
      ),
      body: Center(
        child: Text("Hello, Home!"),
      ),
    );
  }
}


/// lib/presentation/search/pages/search_page.dart
import 'package:flutter/material.dart';

class SearchPage extends StatelessWidget {
  static Route<dynamic> route() => MaterialPageRoute(
        builder: (context) => SearchPage(),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Search"),
      ),
      body: Center(
        child: Text("Hello, Search!"),
      ),
    );
  }
}


/// lib/presentation/product_detail/pages/product_detail_page.dart
import 'package:flutter/material.dart';

class ProductDetailPage extends StatelessWidget {
  static Route<dynamic> route() => MaterialPageRoute(
        builder: (context) => ProductDetailPage(),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Product Detail"),
      ),
      body: Center(
        child: Text("Hello, Product!"),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

TabPage and BottomNavigationBar

Now that we've got the ability to show various pages (i.e. HomePage, SearchPage, and so on), we'll create a TabPage. This page will use IndexedStack to display a different body depending on the current tab selected by the user.

In order to make this easier to generate, let's create a TabNavigationItem to hold information about our tab:

/// lib/presentation/tabs/models/tab_navigation_item.dart
import 'package:flutter/widgets.dart';

class TabNavigationItem {
  final Widget page;
  final Widget title;
  final Icon icon;

  TabNavigationItem({
    @required this.page,
    @required this.title,
    @required this.icon,
  });

  static List<TabNavigationItem> get items => [
        TabNavigationItem(
          page: HomePage(),
          icon: Icon(Icons.home),
          title: Text("Home"),
        ),
        TabNavigationItem(
          page: ShopPage(),
          icon: Icon(Icons.shopping_basket),
          title: Text("Shop"),
        ),
        TabNavigationItem(
          page: SearchPage(),
          icon: Icon(Icons.search),
          title: Text("Search"),
        ),
      ];
}

Enter fullscreen mode Exit fullscreen mode

We can then use this and our other pages to create a TabsPage which will use our aforementioned IndexedStack with a _currentIndex that changes whenever a user taps a tab.

import 'package:flutter/material.dart';
import 'package:flutter_shopping/presentation/home/pages/home_page.dart';
import 'package:flutter_shopping/presentation/search/pages/search_page.dart';
import 'package:flutter_shopping/presentation/shop/pages/shop_page.dart';
import 'package:flutter_shopping/presentation/tabs/models/tab_navigation_item.dart';

class TabsPage extends StatefulWidget {
  @override
  _TabsPageState createState() => _TabsPageState();
}

class _TabsPageState extends State<TabsPage> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: [
          for (final tabItem in TabNavigationItem.items) tabItem.page,
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (int index) => setState(() => _currentIndex = index),
        items: [
          for (final tabItem in TabNavigationItem.items)
            BottomNavigationBarItem(
              icon: tabItem.icon,
              title: tabItem.title,
            )
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

How does IndexedStack work?

Given a list of Widgets (i.e. the children of HomePage, ShopPage and SearchPage), it'll display the Widget where the children matches the currentIndex.

For example, if our currentIndex was 1 and our children array looked like:

children: [
  HomePage()
  ShopPage()
  SearchPage()
]
Enter fullscreen mode Exit fullscreen mode

Our IndexedStack would display the ShopPage.

We'll now be able to update our main.dart to place TabsPage as our home entry:

import 'package:flutter/material.dart';
import 'package:flutter_shopping/presentation/tabs/pages/tabs_page.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App',
      theme: ThemeData(
        primarySwatch: Colors.green,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TabsPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

This gives us the following result:

Summary

We've now used the IndexedStack to switch our displayed widget depending on the item selected by the user!

Code for this article: https://github.com/PaulHalliday/flutter_indexed_stack_tab_view

Top comments (1)

Collapse
 
ninoxboobook profile image
ninoxboobook

I've been teaching myself Flutter and have struggled to wrap my head around getting the bottom navigation bar to work. This was crazy clear and simple. Thank you!