How to use NestedScrollView, TabBarView and AutomaticKeepAliveClientMixin in SliverAppBar Flutter

Dung Nguyen
3 min readNov 23, 2021

--

Introduction

Hi everyone, let me introduce myself a little bit. I’m Dung Nguyen, fresh graduated from Ho Chi Minh University of Science.

I see that there are many bugs when we use these NestedScrollView, TabBarView, AutomaticKeepAliveClientMixin together.
Like this issue or some worse issues in the old Flutter version.
And today, I will share with you guys about How to use NestedScrollView, TabBarView, and AutomaticKeepAliveClientMixin in SliverAppBar.

First of all, I will create a simple demo for NestedScrollView and TabBarView.
The app will look like this:

At this time, the tabs don’t implement AutomaticKeepAliveClientMixin and everything works well.

What does the bug look like?

And now I will implement AutomaticKeepAliveClientMixin for every tab:

class _KantoTabState extends State<KantoTab> with AutomaticKeepAliveClientMixin<KantoTab> {
//...
@override
bool get wantKeepAlive => true;
}

Now the bug appears:

The scroll will affect all the tabs.

In the old Flutter version, the bug is worse than this. We even can’t scroll in the when we switch to a new tab. And the logs throw exceptions about the NestedScrollView can’t give scroll controller to the active tab. The root cause is the first tab is holding the NestedScrollView scroll controller, and it’s still there because of AutomaticKeepAliveClientMixin. That why the bugs doesn’t appear when we don’t implement this Mixin.

How do we fix those bugs?

The solution in this story will help you guys fix all tabs scroll affected and the first tab holding scroll controller bugs.

1. We won’t use the NestedScrollView scroll controller anymore.

ScrollController _scrollController = ScrollController();
//...
CustomScrollView(
controller: _scrollController,
);
//...

Now you will see that the scroll doesn’t affect the SliverAppBar anymore. The tab scroll view is now use its scroll controller.

2. Absorb the inner tab scroll.

First, we need to alias the GlobalKey to NestedScrollView

return Scaffold(
body: NestedScrollView(
key: Keys.mainScreenNestedScrollViewKey,
/...

Next, we will create an ScrollAbsorber.

class ScrollAbsorber {
static void absorbScrollNotification(Notification notification) {
NestedScrollView nestedScrollView =
Keys.nestedScrollViewKey.currentWidget as NestedScrollView;
double scrolled = 0;

//We just need absorb the vertical scroll
if (notification is OverscrollNotification) {
if (notification.metrics.axis == Axis.vertical)
scrolled = notification.overscroll;
}
if (notification is ScrollUpdateNotification) {
if (notification.metrics.axis == Axis.vertical)
scrolled = notification.scrollDelta ?? 0;
}

ScrollController primaryScrollController = nestedScrollView.controller!;
primaryScrollController.jumpTo(primaryScrollController.offset + scrolled);
}
}

Finally, we will absorb the scroll in every tab.

NotificationListener(
onNotification: (Notification notification) {
ScrollAbsorber.absorbScrollNotification(notification);
return true;
},
child: CustomScrollView(
controller: _scrollController,
//...

And the result is:

Conclusion

So the solution basically fixes the bugs in both new and old Flutter versions.
But this solution still contains some hidden bugs like the SliverAppBar disappear when we overscroll to the beginning of InfinityScrollView.

If you use this solution in InfinityScrollView, just avoid absorbing the scroll when its offset is negative and the scroll delta is positive (If my memory is still correct :D, if not just log it you will find out how to prevent this bug).

You can also adjust the absorber to change the pin, floating, snap mode.

Here is the code GitHub link.

Good luck and thanks for reading.

--

--