Repository navigation
Description
I am facing issues with NestedScrollView with multiple scrollable areas, specifically in TabBarView, while enabled Scrollbar either by explicitly wrapping in Scrollbar() widget or overriding ScrollBehavior.buildScrollbar.
This issue is present even in NestedScrollView's demo, which also works like a repro for my case https://api.flutter-io.cn/flutter/widgets/NestedScrollView-class.html#widgets.NestedScrollView.1 and is present on all platforms I tested, specifically on web, iOS, Android, ...
How to reproduce
Scroll in one tab and move (fast) to other one and try to scroll there. Sooner or later an exception will throw, which does no side effect other than being printed, so the app works just fine.
On mobile, wrap NestedScrollView in ScrollConfiguration and use custom behavior
class CustomScrollBehavior extends MaterialScrollBehavior {
@override
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
return Scrollbar(
controller: details.controller,
child: child,
);
}
}
Logs
Mobile logs
======== Exception caught by animation library =====================================================
The following assertion was thrown while notifying status listeners for AnimationController:
The provided ScrollController is currently attached to more than one ScrollPosition.
The Scrollbar requires a single ScrollPosition in order to be painted.
When the scrollbar is interactive, the associated ScrollController must only have one ScrollPosition attached.The provided ScrollController must be unique to one ScrollView widget.
When the exception was thrown, this was the stack:
#0 RawScrollbarState._debugCheckHasValidScrollPosition.<anonymous closure> (package:flutter/src/widgets/scrollbar.dart:1536:9)
#1 RawScrollbarState._debugCheckHasValidScrollPosition (package:flutter/src/widgets/scrollbar.dart:1561:6)
#2 RawScrollbarState._validateInteractions (package:flutter/src/widgets/scrollbar.dart:1470:14)
#3 AnimationLocalStatusListenersMixin.notifyStatusListeners (package:flutter/src/animation/listener_helpers.dart:240:19)
#4 AnimationController._checkStatusChanged (package:flutter/src/animation/animation_controller.dart:822:7)
#5 AnimationController._tick (package:flutter/src/animation/animation_controller.dart:838:5)
#6 Ticker._tick (package:flutter/src/scheduler/ticker.dart:249:12)
#7 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
#8 SchedulerBinding.handleBeginFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:1079:11)
#9 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:617:13)
#10 SchedulerBinding.handleBeginFrame (package:flutter/src/scheduler/binding.dart:1077:17)
#11 SchedulerBinding._handleBeginFrame (package:flutter/src/scheduler/binding.dart:994:5)
#12 _invoke1 (dart:ui/hooks.dart:167:13)
#13 PlatformDispatcher._beginFrame (dart:ui/platform_dispatcher.dart:296:5)
#14 _beginFrame (dart:ui/hooks.dart:104:31)
The AnimationController notifying status listeners was: AnimationController#39240(⏭ 1.000; paused)
====================================================================================================
Web logs
======== Exception caught by animation library =====================================================
The following assertion was thrown while notifying status listeners for AnimationController:
The provided ScrollController is currently attached to more than one ScrollPosition.
The Scrollbar requires a single ScrollPosition in order to be painted.
When the scrollbar is interactive, the associated ScrollController must only have one ScrollPosition attached.The provided ScrollController must be unique to one ScrollView widget.
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 266:49 throw_
packages/flutter/src/widgets/scrollbar.dart 1536:9 <fn>
packages/flutter/src/widgets/scrollbar.dart 1560:14 [_debugCheckHasValidScrollPosition]
packages/flutter/src/widgets/scrollbar.dart 1470:14 [_validateInteractions]
packages/flutter/src/animation/listener_helpers.dart 240:19 notifyStatusListeners
packages/flutter/src/animation/animation_controller.dart 822:7 [_checkStatusChanged]
packages/flutter/src/animation/animation_controller.dart 756:5 [_startSimulation]
packages/flutter/src/animation/animation_controller.dart 619:12 [_animateToInternal]
packages/flutter/src/animation/animation_controller.dart 467:12 forward
packages/flutter/src/widgets/scrollbar.dart 1863:37 [_handleScrollNotification]
packages/flutter/src/widgets/notification_listener.dart 130:22 onNotification
packages/flutter/src/widgets/framework.dart 3094:18 dispatchNotification
packages/flutter/src/widgets/framework.dart 3097:13 dispatchNotification
packages/flutter/src/widgets/framework.dart 4441:24 dispatchNotification
packages/flutter/src/widgets/notification_listener.dart 60:13 dispatch
packages/flutter/src/widgets/scroll_activity.dart 92:90 dispatchScrollUpdateNotification
packages/flutter/src/widgets/scroll_position.dart 914:5 didUpdateScrollPositionBy
packages/flutter/src/widgets/nested_scroll_view.dart 1319:5 applyClampedPointerSignalUpdate
packages/flutter/src/widgets/nested_scroll_view.dart 964:45 pointerScroll
packages/flutter/src/widgets/nested_scroll_view.dart 1417:24 pointerScroll
packages/flutter/src/widgets/scrollable.dart 742:16 [_handlePointerScroll]
packages/flutter/src/gestures/pointer_signal_resolver.dart 90:7 resolve
packages/flutter/src/gestures/binding.dart 466:29 handleEvent
packages/flutter/src/gestures/binding.dart 440:14 dispatchEvent
packages/flutter/src/rendering/binding.dart 337:11 dispatchEvent
packages/flutter/src/gestures/binding.dart 395:7 [_handlePointerEventImmediately]
packages/flutter/src/gestures/binding.dart 357:5 handlePointerEvent
packages/flutter/src/gestures/binding.dart 314:7 [_flushPointerEventQueue]
packages/flutter/src/gestures/binding.dart 295:7 [_handlePointerDataPacket]
lib/_engine/engine/platform_dispatcher.dart 1183:13 invoke1
lib/_engine/engine/platform_dispatcher.dart 244:5 invokeOnPointerDataPacket
lib/_engine/engine/pointer_binding.dart 147:39 [_onPointerData]
lib/_engine/engine/pointer_binding.dart 372:14 [_handleWheelEvent]
lib/_engine/engine/pointer_binding.dart 672:7 <fn>
lib/_engine/engine/pointer_binding.dart 362:43 <fn>
lib/_engine/engine/pointer_binding.dart 203:80 <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 334:14 _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 339:39 dcall
The AnimationController notifying status listeners was: AnimationController#3369f(▶ 0.000)
====================================================================================================
Thoughts
The exceptions tells the reason. Both CustomScrollView, ListView or whatever are using default (primary) ScrollController.
Simple fix might be to add a ScrollController to each scrollable in TabBarView, yet that stops NestedScrollView's headerSliverBuilder, which stops expanding and shrinking itself. The question/report here is a question how to solve/fix this issue? I am not sure that any idea of syncing would work out great with all the slivers capabilities. Or is there some "super magic trick" about using scrollbars in NestedScrollView I am not aware of?
I have seen that there were bunch of issues that were slightly different, mainly because there were not dependent on the NestedScrollView and sliver fancy needs like #88428. Some stuff have been said here, but that is rather about preserving tab states #101320. Or #89864 which is also not about NestedScrollView. Neither of those issues helped my case, unfortunately.
Summary:
- scrolling works just fine, scrollbars seems to work too, but exceptions been raising
- we need to preserve
NestedScrollViewso we can work with slivers based on the unified scroll area - we need scrollbars (for individual tabs)
- this issue is present in linked demo
NestedScrollVieweven advises not to use controllers on nested scrollable
/// The [body] is built in a context that provides a [PrimaryScrollController]
/// that interacts with the [NestedScrollView]'s scroll controller. Any
/// [ListView] or other [Scrollable]-based widget inside the [body] that is
/// intended to scroll with the [NestedScrollView] should therefore not be
/// given an explicit [ScrollController], instead allowing it to default to
/// the [PrimaryScrollController] provided by the [NestedScrollView].
Activity
darshankawar commentedon Jan 19, 2023
Thanks for the report @tenhobi
Your case looks very similar to #101320 as you already mentioned, but have you already tried this and see if it helps in your case ?
I think all these bullet points you can share in this umbrella issue related to
NestedScrollViewso that there will be a proper tracking for all the thoughts you shared and the team member will be aware of it.Let us know if this sounds ok and can be clubbed together with above linked issue, because per your summary, it seems they are multiple use cases / issues which probably need to be tracked separately as well.
tenhobi commentedon Jan 19, 2023
Thanks for a reply @darshankawar.
Yes, I tried that with the linked demo, aka

As you can see from the recording, the scrollbar then applies only to outer scrollable, not including the inner ones. Same behaviour is on the web.
Screen.Recording.2023-01-19.at.8.23.54.AM.mov
darshankawar commentedon Jan 20, 2023
Thanks for the feedback. I am going to keep this issue open to team's insights on what's the best way to tackle such issues. There have been similar issues reported as we discussed above #101320, but per OP, the mentioned solution in it doesn't apply for their case.
The main issue could be related to #102003, but will let the team take a call on it whether to club this issue together or keep it separate.
code sample
stable, master flutter doctor -v
darshankawar commentedon Jan 20, 2023
/cc @Piinks
AsturaPhoenix commentedon Feb 3, 2023
@tenhobi You might be able to get somewhere by wrapping the inner
ScrollControllers in aScrollControllerthat effectively keeps track of its own dedicatedScrollPositionbut also attaches it to the inner scroll controller provided by theNestedScrollView, to keep both the scrollbars and theNestedScrollViewhappy:https://dartpad.dev/8218ccd847e33d5dd29417b19866a63f
It's not pretty though, and requires, in addition to the adaptor scroll controller, that you intercept the inner scroll controller to wrap it for each inner scrollable (examples in the DartPad).
SubordinateScrollControllerWrapping the inner scroll controllers
This is more of a hack than a solution though.
A cleaner way to wrap the inner scroll controller could be to make each tab view a
StatefulWidgetthat keeps its own wrapped scroll controller and recreates it based ondidUpdateWidget, but you'd still need to pull it out usingPrimaryScrollController.of(context).Piinks commentedon May 30, 2023
The issue here I think is #81152, the PrimaryScrollController is what the NestedScrollView relies on to attach to the inner scroll view of the body.
Thank you for providing another test case for us as we work on trying to fix it. I am going to close this to refer to #81152. Thanks!
github-actions commentedon Jun 19, 2023
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.