Flutter has a TabPageSelector
widget which is used to show dots indicating how many pages are available to view and which page you are currently at.
▫ ▫ ▫ ▪ ▫
Unfortunately, it is only designed to hook into a TabBarView
.
I wanted to use the dots indicator on a PageView
widget, which are both similar looking widgets, but their controllers are incompatible.
💡 TabBarView is for creating a UI with tabs at the top. PageView is for creating a UI based on separate pages like a book.
-
PageView
only accepts aPageController
-
TabPageSelector
only accepts aTabController
.
The former is a subclass of ScrollController
while the latter is a subclass of ChangeNotifier
, so both are completely incompatible.
Bridge the two
If I wanted to bridge the two, PageView
has a callback called onPageChanged(int)
and TabController
has a writable index
property.
It's simply a case of:
PageView.builder(
onPageChanged: (index) => _tabController.index = index,
//...
)
But TabController
doesn't allow you to update the total length of pages after it has already been created. The only way to change it is to dispose it and then re-create it.
DefaultTabController
Another option is to use the DefaultTabController
inherited widget which automatically handles creation and disposing.
But how do we get the handle to the DefaultTabController
in the first place?
Because it is an inherited widget, we can use the DefaultTabController.of
to reach down BuildContext
to get the nearest instance, like this:
DefaultTabController.of(context)!.index = index;
In total, the implementation looks like this:
//assuming this data
var pages = ["a", "b", "c"];
Widget build(BuildContext context) {
return DefaultTabController(
length: pages.length,
child: Column(
children: [
PageView.builder(
itemCount: pages.length,
onPageChanged: (index) {
DefaultTabController.of(context)!.index = index;
}
itemBuilder(_, index) {
return Text(pages[index]);
}
),
TabPageSelector(),
],
),
);
}
When the user swipes to a different page, we get the DefaultTabController
and update the index, which tells TabPageSelector
to also update the dot indicators.
Holup
The astute readers among you will notice that climbing the BuildContext
to the ancestor inherited widget wouldn't work when both are using the same BuildContext
.
The below call would throw a null de-reference error at the bang.
DefaultTabController.of(context)!.index
To make it work, we need to insert another Builder
in between the two, which will force the creation of another BuildContext
.
In total, the implementation looks like this:
Widget build(BuildContext context) {
return DefaultTabController(
length: pages.length,
child: Builder(builder: (context) {
return Column(
children: [
PageView.builder(
itemCount: pages.length,
onPageChanged: (index) {
DefaultTabController.of(context)!.index = index;
}
itemBuilder(_, index) {
return Text(pages[index]);
}
),
TabPageSelector(),
],
);
}),
);
}
Happy coding!
Top comments (0)