How do you make a TabBar in flutter?

TabBar is a material design widget that displays a horizontal row of tabs, and we can control the number of tabs.

We create the horizontal row of tabs as the AppBar.bottom part of an AppBar. Moreover, we need two extra widgets to fulfill our mission.

The first one is TabController, and if that is not provided, then we should use a DefaultTabController as the ancestor.

In Addition we need a TabBarView that returns a list of widgets where we can place the associated tab views.

While building the TabBar widget, we always remember a few key concepts. The tab controller’s TabController.length must equal the length of the tabs list and the length of the TabBarView.children list.

Let us imagine a two tabs “Home” and “Contact”. When we run the app, the Home tab initially selected and opens up. However, just below the AppBar we can have two tabs displayed side by side.

TabBar flutter two tabs
TabBar flutter two tabs

The AppBar bottom property holds the key to two tabs displayed in the image above.

return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: const Text(
              'Scaffold Example',
              style: TextStyle(
                fontFamily: 'Allison',
              ),
            ),
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.home_max_outlined), text: "Home"),
                Tab(icon: Icon(Icons.contact_page_outlined), text: "Contact")
              ],
            ),
          ),
...
// code is incomplete for brevity

In the above image what we see is this part of the code:

bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.home_max_outlined), text: "Home"),
                Tab(icon: Icon(Icons.contact_page_outlined), text: "Contact")
              ],
            ),

Although this code snippet is incomplete, we can take a look at the full code at this GitHub repository.

Therefore, a TabBar has two distinct parts. Firstly we need DefaultTabController as the ancestor. Secondly we need TabBarView widget to return the body part of the Scaffold.

As a result the full code looks similar to this.

import 'package:flutter/material.dart';
import 'tabbar_contact.dart';
import 'tabbar_home.dart';

class TabBarExample extends StatelessWidget {
  const TabBarExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: const Text(
              'Scaffold Example',
              style: TextStyle(
                fontFamily: 'Allison',
              ),
            ),
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.home_max_outlined), text: "Home"),
                Tab(icon: Icon(Icons.contact_page_outlined), text: "Contact")
              ],
            ),
          ),
          body: TabBarView(
            children: [
              TabBarHome(),
              TabBarContact(),
            ],
          ),
          drawer: Drawer(
            child: ListView(
              // Important: Remove any padding from the ListView.
              padding: EdgeInsets.zero,
              children: <Widget>[
                const UserAccountsDrawerHeader(
                  accountName: Text("Sanjib Sinha"),
                  accountEmail: Text("sanjib@sanjibsinha.com"),
                  currentAccountPicture: CircleAvatar(
                    backgroundColor: Colors.orange,
                    child: Text(
                      "S",
                      style: TextStyle(fontSize: 40.0),
                    ),
                  ),
                ),
                ListTile(
                  leading: const Icon(Icons.home),
                  title: const Text("Home"),
                  onTap: () {
                    Navigator.pop(context);
                  },
                ),
                ListTile(
                  leading: const Icon(Icons.settings),
                  title: const Text("About"),
                  onTap: () {
                    Navigator.pop(context);
                  },
                ),
                ListTile(
                  leading: const Icon(Icons.contacts),
                  title: const Text("Contact Us"),
                  onTap: () {
                    Navigator.pop(context);
                  },
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            elevation: 10.0,
            child: const Icon(Icons.access_alarms_outlined),
            onPressed: () {},
          ),
          floatingActionButtonLocation:
              FloatingActionButtonLocation.centerFloat,
          persistentFooterButtons: <Widget>[
            TextButton(
              onPressed: () {},
              child: const Icon(
                Icons.backpack_rounded,
                color: Colors.green,
              ),
            ),
            TextButton(
              onPressed: () {},
              child: const Icon(
                Icons.handyman_rounded,
                color: Colors.red,
              ),
            ),
          ],
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: 0,
            fixedColor: Colors.teal,
            items: const [
              BottomNavigationBarItem(
                label: 'Home',
                icon: Icon(Icons.home),
              ),
              BottomNavigationBarItem(
                label: 'Search',
                icon: Icon(Icons.search),
              ),
              BottomNavigationBarItem(
                label: 'Add',
                icon: Icon(Icons.add_box),
              ),
            ],
            onTap: (int index) {},
          ),
          backgroundColor: Colors.amberAccent,
        ),
      ),
    );
  }
}

In the above code snippet, the body part plays an important role also.

 body: TabBarView(
            children: [
              TabBarHome(),
              TabBarContact(),
            ],
          ),

We need to have two separate widgets to show two pages once the tabs are clicked.

What is TabBarView flutter?

The TabBarView is nothing but a page that displays the particular widget that corresponds to the selected tab. If we select the Home tab, we get this view.

TabBar flutter home TabView
TabBar flutter home TabBarView

A page view that displays the widget which corresponds to the currently selected tab. As we’ve learned earlier, we use the TabBarView in conjunction with a TabBar.

We can take a look at the home page code. That is pretty simple.

import 'package:flutter/material.dart';

class TabBarHome extends StatelessWidget {
  const TabBarHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(20),
      padding: const EdgeInsets.all(20),
      child: Text(
        'Home Page',
        style: TextStyle(
          fontFamily: "Allison",
          fontSize: 50,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

If we select the contact tab, we can see the contact page.

In the similar vein, we can write the same code changing slightly.

import 'package:flutter/material.dart';

class TabBarContact extends StatelessWidget {
  const TabBarContact({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(20),
      padding: const EdgeInsets.all(20),
      child: Text(
        'Conact Page',
        style: TextStyle(
          fontFamily: "Allison",
          fontSize: 50,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

As a result, we can view the contact page like the following image.

TabBar flutter contact tab
TabBar flutter contact TabBarView

While working with TabBar and a TabBarView, we need TabController, and if that is not provided, then we should use a DefaultTabController as the ancestor.

The TabController class coordinates tab selection between a TabBar and a TabBarView.

The index property is the index of the selected tab.

In our case, how did we use that?

We’ve not mentioned that. However, in case we use a List of widgets beforehand, then we can select a current index.

Consequently we need to remember that the TabBar inherits from a stateful widget and therefore can create a TabController and share it directly.

In our case, since the TabBar and TabBarView don’t have a convenient stateful ancestor, we’ve provided a DefaultTabController inherited widget.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

Leave a Reply