friend.code
friend.code

Reputation: 89

How to use CustomScrollView in a GoRouter ShellRoute?

I had the idea to create a CustomScrollView within a GoRouter's ShellRoute so that I could have a reusable SliverAppBar that doesn't have to be re-rendered on every route. Unfortunately, returning a SliverList from the GoRoute within a ShellRoute causes issues because GoRouter embeds the Widget returned by GoRoute#builder into some other classes...

Minimal example:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    var title = 'Flutter Demo';
    return MaterialApp.router(
        title: title,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routerConfig:
            GoRouter(navigatorKey: GlobalKey<NavigatorState>(), routes: [
          ShellRoute(
              navigatorKey: GlobalKey<NavigatorState>(),
              builder:
                  (BuildContext context, GoRouterState state, Widget child) {
                return CustomScrollView(slivers: [
                  SliverAppBar(
                    expandedHeight: 300.0,
                    flexibleSpace: FlexibleSpaceBar(
                      background: Container(
                        color: Colors.red,
                        child: const Text("Shrinkable"),
                      ),
                    ),
                  ),
                  child
                ]);
              },
              routes: [
                GoRoute(
                    path: "/",
                    builder: (BuildContext context, GoRouterState state) {
                      return SliverList(
                          delegate:
                              SliverChildListDelegate([const Text("data")]));
                    })
              ])
        ]));
  }
}

Error logs:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building NotificationListener<NavigationNotification>:
A RenderViewport expected a child of type RenderSliver but received a child of type RenderPointerListener.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderPointerListener that did not match the expected child type was created by: Listener ← NotificationListener<NavigationNotification> ← HeroControllerScope ← Navigator-[LabeledGlobalKey<NavigatorState>#0220d] ← HeroControllerScope ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← ⋯
The relevant error-causing widget was: 
  Navigator-[LabeledGlobalKey<NavigatorState>#0220d] Navigator:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:429:16

======== Exception caught by widgets library =======================================================
The following assertion was thrown building HeroControllerScope:
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#4bbd6] ← NotificationListener<NavigationNotification> ← HeroControllerScope ← Navigator-[LabeledGlobalKey<NavigatorState>#0220d] ← HeroControllerScope ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← ⋯
The relevant error-causing widget was: 
  Navigator-[LabeledGlobalKey<NavigatorState>#0220d] Navigator:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:429:16

======== Exception caught by widgets library =======================================================
The following assertion was thrown building Navigator-[LabeledGlobalKey<NavigatorState>#0220d](dependencies: [HeroControllerScope, UnmanagedRestorationScope], state: NavigatorState#e0a51(tickers: tracking 1 ticker)):
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#9b449] ← HeroControllerScope ← Navigator-[LabeledGlobalKey<NavigatorState>#0220d] ← HeroControllerScope ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← ⋯
The relevant error-causing widget was: 
  Navigator-[LabeledGlobalKey<NavigatorState>#0220d] Navigator:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:429:16

======== Exception caught by widgets library =======================================================
The following assertion was thrown building HeroControllerScope:
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#dbfa8] ← Navigator-[LabeledGlobalKey<NavigatorState>#0220d] ← HeroControllerScope ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← ⋯
The relevant error-causing widget was: 
  HeroControllerScope HeroControllerScope:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:427:14

======== Exception caught by widgets library =======================================================
The following assertion was thrown building GoRouterStateRegistryScope:
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#a23b2] ← HeroControllerScope ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← ⋯
The relevant error-causing widget was: 
  GoRouterStateRegistryScope GoRouterStateRegistryScope:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:425:12

======== Exception caught by widgets library =======================================================
The following assertion was thrown building _CustomNavigator-[GlobalObjectKey int#0220d](state: _CustomNavigatorState#4988d):
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#2da42] ← GoRouterStateRegistryScope ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← ⋯
The relevant error-causing widget was: 
  _CustomNavigator-[GlobalObjectKey int#0220d] _CustomNavigator:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:276:16

======== Exception caught by widgets library =======================================================
The following assertion was thrown building RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46](state: RawGestureDetectorState#cc3be(gestures: <none>, behavior: opaque)):
A RenderViewport expected a child of type RenderSliver but received a child of type RenderErrorBox.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderViewport that expected a RenderSliver child was created by: Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← RepaintBoundary ← CustomPaint-[GlobalKey#dedf6] ← ⋯
The RenderErrorBox that did not match the expected child type was created by: ErrorWidget-[#413d0] ← _CustomNavigator-[GlobalObjectKey int#0220d] ← Viewport ← IgnorePointer-[GlobalKey#e9fba] ← Semantics ← Listener ← _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#e2d46] ← Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#e77c7] ← NotificationListener<ScrollMetricsNotification> ← ⋯
The relevant error-causing widget was: 
  CustomScrollView CustomScrollView:file:///D:/code/playground/flutter/go_router_slivers/lib/main.dart:26:24

If I try to wrap the ShellRoute#child inside of a SliverToBoxAdapter, then I get the sort of reverse error, albeit a much smaller one:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    var title = 'Flutter Demo';
    return MaterialApp.router(
        title: title,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routerConfig:
            GoRouter(navigatorKey: GlobalKey<NavigatorState>(), routes: [
          ShellRoute(
              navigatorKey: GlobalKey<NavigatorState>(),
              builder:
                  (BuildContext context, GoRouterState state, Widget child) {
                return CustomScrollView(slivers: [
                  SliverAppBar(
                    expandedHeight: 300.0,
                    flexibleSpace: FlexibleSpaceBar(
                      background: Container(
                        color: Colors.red,
                        child: const Text("Shrinkable"),
                      ),
                    ),
                  ),
                  SliverToBoxAdapter(child: child) // <-- wrapped this time
                ]);
              },
              routes: [
                GoRoute(
                    path: "/",
                    builder: (BuildContext context, GoRouterState state) {
                      return SliverList(
                          delegate:
                              SliverChildListDelegate([const Text("data")]));
                    })
              ])
        ]));
  }
}

Error logs:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building Builder:
A RenderSemanticsAnnotations expected a child of type RenderBox but received a child of type RenderSliverList.

RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
The RenderSemanticsAnnotations that expected a RenderBox child was created by: Semantics ← Builder ← RepaintBoundary-[GlobalKey#fd14b] ← IgnorePointer ← AnimatedBuilder ← SnapshotWidget ← _ZoomExitTransition ← SnapshotWidget ← _ZoomEnterTransition ← DualTransitionBuilder ← SnapshotWidget ← _ZoomExitTransition ← ⋯
The RenderSliverList that did not match the expected child type was created by: SliverList ← Builder ← Semantics ← Builder ← RepaintBoundary-[GlobalKey#fd14b] ← IgnorePointer ← AnimatedBuilder ← SnapshotWidget ← _ZoomExitTransition ← SnapshotWidget ← _ZoomEnterTransition ← DualTransitionBuilder ← ⋯
The relevant error-causing widget was: 
  Builder Builder:file:///C:/Users/Pete/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.2.3/lib/src/builder.dart:256:9

This is my first foray into the land of Slivers, so I'm at a loss. Do I need to give up and just rebuild the CustomScrollView on each individual route, along with the SliverAppBar? Any advice is appreciated. TIA :)

Upvotes: 0

Views: 201

Answers (1)

Hippo Fish
Hippo Fish

Reputation: 708

So the problem you're facing is from the child route. Bcz, you're passing a sliver to the route builder, where it expects a widget. Here you're passing a SliverList.

That is a sliver, not a regular widget.

Right now, there are two simple ways to fix it. First one is -

Do what you're already doing. And just wrap your SliverList with a CustomScrollView. Just to convert the SliverList to a regular widget. But it will overflow. If you look at the layout it was supposed to overflow anyway. Bcz you converted a regular widget to a sliver. But the regular widget doesn't define how long it will be on the scroll direction.

But yeah the code will look something like this -



import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    var title = 'Flutter Demo';
    return MaterialApp.router(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      routerConfig: GoRouter(
        navigatorKey: GlobalKey<NavigatorState>(),
        routes: [
          ShellRoute(
              navigatorKey: GlobalKey<NavigatorState>(),
              builder:
                  (BuildContext context, GoRouterState state, Widget child) {
                return CustomScrollView(slivers: [
                  SliverAppBar(
                    expandedHeight: 300.0,
                    flexibleSpace: FlexibleSpaceBar(
                      background: Container(
                        color: Colors.red,
                        child: const Text("Shrinkable"),
                      ),
                    ),
                  ),
                  SliverToBoxAdapter(
                    child: child, // * You can define a height here too. To tell the Sliver that my regular widget will be x/y long.
                   ) // <-- wrapped this time
                ]);
              },
              routes: [
                GoRoute(
                    path: "/",
                    builder: (BuildContext context, GoRouterState state) {
                      return SizedBox(
                       height: 500; // However, I have defined the height here.
                        child: CustomScrollView(
                          slivers: [
                            SliverList(
                              delegate: SliverChildListDelegate(
                                [
                                  const Text("data"),
                                ],
                              ),
                            ),
                          ],
                        ),
                      );
                    })
              ])
        ],
      ),
    );
  }
}

But the other way you can do the same thing is to pass a widget variant of the SliverList to the GoRoute builder. Which will be a regular ListView widget. And for that the code should look something like this.

GoRoute(
     path: "/",
     builder: (BuildContext context, GoRouterState state) {
       return SizedBox(
         height: 500,
         child: ListView(
           children: const [
             Text("data"),
           ],
         ),
       );
     },
   )

Hope it helps! Happy coding!💙

Upvotes: 2

Related Questions