Blog · · 1 min read

Flutter TabBar Example – WeightTracker 5

In this short post, I will document how I added TabBar to my WeightTracker app.

Note: I am using Redux to populate data to view, if you are not familiar with Redux, just ignore StoreConnector widget and focus only on Builder body which can be treated like normal build function.

In order to add Tabs to the application, all I needed was to create TabBar and TabBarView and attach TabController to them. TabController will sync TabBar with TabBarView so we can have the behavior we wanted.

main_page.dart
class MainPageState extends State<MainPage> with SingleTickerProviderStateMixin{
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: 2);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new StoreConnector<ReduxState, MainPageViewModel>(
converter: (store) {
return new MainPageViewModel([...]);
},
builder: (context, viewModel) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
bottom: new TabBar(
tabs: <Tab>[
new Tab(
text: "STATISTICS",
icon: new Icon(Icons.show_chart),
),
new Tab(
text: "HISTORY",
icon: new Icon(Icons.history),
),
],
controller: _tabController,
),
),
body: new TabBarView(
children: <Widget>[
new StatisticsPage(),
new HistoryPage(),
],
controller: _tabController,
),
floatingActionButton: [...]
);
},
);
}
}


However, I didn’t want the title widget to stick on the top of the screen forever. To add nice hiding behavior, I introduced NestedScrollView with SliverAppBar:

main_page.dart
class MainPageState extends State<MainPage> with SingleTickerProviderStateMixin {
ScrollController _scrollViewController;
TabController _tabController;
@override
void initState() {
super.initState();
_scrollViewController = new ScrollController();
_tabController = new TabController(vsync: this, length: 2);
}
@override
void dispose() {
_scrollViewController.dispose();
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new StoreConnector<ReduxState, MainPageViewModel>(
converter: (store) {
return new MainPageViewModel([...]);
},
builder: (context, viewModel) {
return new Scaffold(
body: new NestedScrollView(
controller: _scrollViewController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { //<-- headerSliverBuilder
return <Widget>[
new SliverAppBar(
title: new Text(widget.title),
pinned: true, //<-- pinned to true
floating: true, //<-- floating to true
forceElevated: innerBoxIsScrolled, //<-- forceElevated to innerBoxIsScrolled
bottom: new TabBar(
tabs: <Tab>[
new Tab(
text: "STATISTICS",
icon: new Icon(Icons.show_chart),
),
new Tab(
text: "HISTORY",
icon: new Icon(Icons.history),
),
],
controller: _tabController,
),
),
];
},
body: new TabBarView(
children: <Widget>[
new StatisticsPage(),
new HistoryPage(),
],
controller: _tabController,
),
),
floatingActionButton: [...]
);
},
);
}
}

And that’s it.

If you are interested in the full code shown in this post, see my GitHub repository. 🙂

Cheers! 🙂