Build a Nested TabBar in Flutter

Sarthak Verma
FlutterArsenal
Published in
6 min readJun 25, 2019

TabBars can prove to be a very useful component in any mobile application. Generally, a TabBar appears on the bottom of the screen or sometimes at the top. If you’re not familiar with what a TabBar is then you can think of it as a UI element which consists of a number of options(tabs) and each tab has a different Tab View when active.

TabBar

But what if you want the more than one TabBars on a single screen of your application? In this case, you will need to implement a Nested TabBar and we are going to learn how to do that right away.

Getting Started

Let’s take a look at the result beforehand so that you know what are we building.

Nested TabBar Flutter Demo

First, you have to create a blank Flutter Project. You main.dart file should look something similar to the code below.

main.dart (Blank Flutter App)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Tab Bar Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Nested Tab Bar Demo Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

@override
Widget build(BuildContext context) {

return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(

),
);
}
}

Now let’s add our First TabBar to the application.

First of all, we have to add a TickerProviderStateMixin to the class that defines the state.

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin

Then we have to add a controller for our first TabBar.

TabController _tabController;

Now we should initialize this controller in the initState() method. We have to specify the length of the TabBar while initializing this controller. Let’s set this to 3 for now.

@override
void initState() {
super.initState();
_tabController = new TabController(length: 3, vsync: this);
}

Now, its time that we should give a location to our first TabBar. For this, we can use the bottomNavigationBar option provided by default when we use Scaffold.

bottomNavigationBar: Material(
color: Colors.white,
child: TabBar(
controller: _tabController,
indicatorColor: Colors.teal,
labelColor: Colors.teal,
unselectedLabelColor: Colors.black54,
tabs: <Widget>[
Tab(
icon: Icon(Icons.home),
),
Tab(
icon: Icon(Icons.email),
),
Tab(
icon: Icon(Icons.settings),
),
]),
),

Now let’s add Views for the tabs in this TabBar. For this, inside the body we have to place a TabBarView widget.

body: TabBarView(
children: <Widget>[
Center(
child: Text("Home"),
),
Center(
child: Text("Email"),
),
Center(
child: Text("Settings"),
)
],
controller: _tabController,
),

Now that we have added a view for all the three tabs in our tab bar, we just have to do one last this i.e. to dispose the Tab Controller by overriding the dispose() method.

@override
void dispose() {
super.dispose();
_tabController.dispose();
}

We have now successfully added our first Tab Bar and our main.dart should now look like the code below.

main.dart (After adding First TabBar)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Tab Bar Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Nested Tab Bar Demo Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
TabController _tabController;

@override
void initState() {
super.initState();

_tabController = new TabController(length: 3, vsync: this);
}

@override
void dispose() {
super.dispose();
_tabController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
bottomNavigationBar: Material(
color: Colors.white,
child: TabBar(
controller: _tabController,
indicatorColor: Colors.teal,
labelColor: Colors.teal,
unselectedLabelColor: Colors.black54,
tabs: <Widget>[
Tab(
icon: Icon(Icons.home),
),
Tab(
icon: Icon(Icons.email),
),
Tab(
icon: Icon(Icons.settings),
),
]),
),
body: TabBarView(
children: <Widget>[
Center(
child: Text("Home"),
),
Center(
child: Text("Email"),
),
Center(
child: Text("Settings"),
)
],
controller: _tabController,
),
);
}
}

Now it’s time for implementing the Second TabBar in the TabBar View of any Tab.

For more understanding and code simplicity we will create a separate class for the TabBarView which will consist of our secondTabBar.

Let’s create a file named as nestedTabBarView.dart. We can now add TabBar and TabBarView in this class just like we did it for the first TabBar in main.dart. The only difference here will be that in the build function we will return a Column instead of a Scaffold. The children of this Column will be TabBar and a Container wrapping the TabBarView.

nestedTabBarView.dart

import 'package:flutter/material.dart';

class NestedTabBar extends StatefulWidget {

@override
_NestedTabBarState createState() => _NestedTabBarState();
}

class _NestedTabBarState extends State<NestedTabBar>
with TickerProviderStateMixin {
TabController _nestedTabController;

@override
void initState() {
super.initState();

_nestedTabController = new TabController(length: 5, vsync: this);
}

@override
void dispose() {
super.dispose();
_nestedTabController.dispose();
}

@override
Widget build(BuildContext context) {
double screenHeight = MediaQuery.of(context).size.height;
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
TabBar(
controller: _nestedTabController,
indicatorColor: Colors.teal,
labelColor: Colors.teal,
unselectedLabelColor: Colors.black54,
isScrollable: true,
tabs: <Widget>[
Tab(
text: "One",
),
Tab(
text: "Two",
),
Tab(
text: "Three",
),
Tab(
text: "Four",
),
Tab(
text: "Five",
),
],
),
Container(
height: screenHeight * 0.70,
margin: EdgeInsets.only(left: 16.0, right: 16.0),
child: TabBarView(
controller: _nestedTabController,
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.blueAccent,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.orangeAccent,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.greenAccent,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.indigoAccent,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.redAccent,
),
),
],
),
)
],
);
}
}

Lastly, in the main.dart import the NestedTabBar class and any child of the TabBarView with the class name.

main.dart (Final)

import 'package:flutter/material.dart';
import 'package:nested_tabbar_flutter/nestedTabBarView.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Tab Bar Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Nested Tab Bar Demo Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
TabController _tabController;

@override
void initState() {
super.initState();

_tabController = new TabController(length: 3, vsync: this);
}

@override
void dispose() {
super.dispose();
_tabController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
bottomNavigationBar: Material(
color: Colors.white,
child: TabBar(
controller: _tabController,
indicatorColor: Colors.teal,
labelColor: Colors.teal,
unselectedLabelColor: Colors.black54,
tabs: <Widget>[
Tab(
icon: Icon(Icons.home),
),
Tab(
icon: Icon(Icons.email),
),
Tab(
icon: Icon(Icons.settings),
),
],
),
),
body: TabBarView(
children: <Widget>[
NestedTabBar(),
Center(
child: Text("Email"),
),
Center(
child: Text("Settings"),
)
],
controller: _tabController,
),
);
}
}

If you will run the app now, you will find that you have successfully implemented a Nested TabBar in your Flutter Application.

Important Points

You might have noticed the below line of code in the build function in the nestedTabBarView.dart.

double screenHeight = MediaQuery.of(context).size.height;

The need for this line of code is that it helps to find the screen height of the device in which the application is running and store it in a double type variable with the name screenHeight. This variable can be later used to give heights to different widgets corresponding to the screen height.

Github Link

The code for this build is available on Github. You can view it at:

Nested TabBar - Flutter

Done

In case you have any doubts or question, I’ll be happy to help. Also, if you like this post you can follow me on GITHUB.

--

--

Sarthak Verma
FlutterArsenal

Developer | Designer | Blogger | Tech-Enthusiast | Explorer | Innovator