idootop/nested_scroll_view_plus

Support for multiple OverlapInjectors

Closed this issue · 2 comments

The problem is as follows:

When there are multiple slivers inside the headerSliverBuilder and you try to inject both of those widget's OverlapAbsorberPlus, the following exception is thrown:

══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following assertion was thrown during performLayout():
A OriginalOverlapAbsorberHandle cannot be passed to multiple OriginalRenderSliverOverlapAbsorber
objects at the same time.
'package:nested_scroll_view_plus/src/nested_scroll_view_outer.dart':
Failed assertion: line 368 pos 7: 'handle._writers == 1'

The relevant error-causing widget was:
SliverOverlapAbsorberPlus
SliverOverlapAbsorberPlus:file:///Users/fleeser/.pub-cache/hosted/pub.dev/nested_scroll_view_plus-1.0.2/lib/src/nested_scroll_view_plus.dart:156:12

When the exception was thrown, this was the stack:
#2 RenderSliverOverlapAbsorberOuter.performLayout (package:nested_scroll_view_plus/src/nested_scroll_view_outer.dart:368:7)
#3 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#4 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:601:13)
#5 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1554:12)
#6 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1463:20)
#7 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#8 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#9 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#10 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#11 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#12 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#13 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#14 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#15 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#16 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#17 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#18 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#19 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#20 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#21 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#22 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#23 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#24 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#25 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#26 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#27 MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:173:12)
#28 _ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:1062:7)
#29 MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:237:7)
#30 RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:403:14)
#31 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#32 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#33 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#34 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#35 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#36 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#37 _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1434:11)
#38 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#39 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#40 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#41 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#42 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#43 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#44 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#45 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#46 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#47 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#48 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#49 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#50 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#51 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#52 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#53 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#54 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#55 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#56 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#57 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#58 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#59 RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3714:13)
#60 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#61 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#62 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
#63 RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
#64 RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
#65 _RenderTheaterMixin.performLayout (package:flutter/src/widgets/overlay.dart:884:15)
#66 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2385:7)
#67 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1025:18)
#68 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1038:15)
#69 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:591:23)
#70 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:986:13)
#71 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:457:5)
#72 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1325:15)
#73 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1255:9)
#74 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1113:5)
#75 _invoke (dart:ui/hooks.dart:312:13)
#76 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:383:5)
#77 _drawFrame (dart:ui/hooks.dart:283:31)
(elided 2 frames from class _AssertionError)

The following RenderObject was being processed when the exception was fired: RenderSliverOverlapAbsorberOuter#67258 relayoutBoundary=up1 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:
creator: SliverOverlapAbsorberPlus ← OverlapAbsorberPlus-[<'profile-overlap-absorber-plus-app-bar'>]
← OriginalNestedScrollViewViewport ← IgnorePointer-[GlobalKey#5c76b] ← Semantics ← Listener ←
_GestureSemantics ← RawGestureDetector-[LabeledGlobalKey#08f42] ←
Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#35de9] ←
NotificationListener ← ⋯
parentData: paintOffset=Offset(0.0, 0.0) (can use size)
constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle,
scrollOffset: 0.0, remainingPaintExtent: 842.0, crossAxisExtent: 430.0, crossAxisDirection:
AxisDirection.right, viewportMainAxisExtent: 842.0, remainingCacheExtent: 1092.0, cacheOrigin:
0.0)
geometry: null
handle: OriginalOverlapAbsorberHandle(null, 2 WRITERS ASSIGNED)
This RenderObject had the following child:
child: _RenderSliverPinnedPersistentHeaderForWidgets#1f375 NEEDS-LAYOUT NEEDS-PAINT
════════════════════════════════════════════════════════════════════════════════════════════════════

Another exception was thrown: Null check operator used on a null value
Another exception was thrown: Null check operator used on a null value

I tried to wrap SliverMainAxisGroup around both header slivers, but the pinned effect won't work anymore. Therefore I thought the solution would be to pass different keys and use them by putting both of them into the CustomScrollView.

This is the code atm:

return Scaffold(
  body: NestedScrollViewPlus(
    headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => [
      OverlapAbsorberPlus(
        key: const Key('profile-overlap-absorber-plus-app-bar'),
        sliver: SCSliverAppBar(...)
      ),
      OverlapAbsorberPlus(
        key: const Key('profile-overlap-absorber-plus-tab-bar'),
        sliver: SCSliverTabBar(...)
      )
    ],
    body: TabBarView(
      controller: _tabController,
      children: [
        CustomScrollView(
          key: const PageStorageKey<String>('profile-events-key'),
          physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
          slivers: [
            const OverlapInjectorPlus(),
            SliverList.builder(...)
          ]
        ),
        CustomScrollView(
          key: const PageStorageKey<String>('profile-ratings-key'),
          physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
          slivers: [
            const OverlapInjectorPlus(key: Key('profile-overlap-absorber-plus-app-bar')),
            const OverlapInjectorPlus(key: Key('profile-overlap-absorber-plus-tab-bar')),
            SliverList.builder(...)
          ]
        )
      ]
    )
  )
);

What I try to achieve:
One of the Tabs should contain another pinned sliver. Currently, when removing the second OverlapAbsorberPlus and OverlapInjectorPlus again, this Widget moves under the TabBar and pins itself directly under the AppBar, because this is the only Widget inside a OverlapAbsorberPlus.

Would love to hear from you!

I was able to resolve this by wrapping both header widgets inside MultiSliver from the sliver_tools package like so:

OverlapAbsorberPlus(
  sliver: MultiSliver(
    children: [
      SCSliverAppBar(...),
      SCSliverTabBar(...)
    ]
  )
)

Would love to see this as a feature instead of using multiple different packages.

Thank you for your feedback. This is a common requirement, and I recommend continuing to use MultiSliver to wrap multiple header slivers, as it is a suitable approach.

Additionally, based on the implementation of OverlapInjector, it is not possible to have multiple OverlapInjector instances coexist within the headerSliverBuilder. This would disrupt the operation of the NestedScrollView. Therefore, using MultiSliver to group your header slivers is a suitable approach.

Thank you for understanding, and feel free to reach out if you have any further questions or concerns. ;)