0

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 :)

1 Answer 1

2

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!💙

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you, this is helpful. I was thinking a Sliver was a special type of Widget... Now, whether I wrap the GoRoute content with CustomScrollView or with ListView, these Widgets need to have separate ScrollControllers. That means I lose the ability to expand & shrink the SliverAppBar in the ShellRoute. Is there any workaround for that?
There are multiple ways you can do that actually. But I guess, the simples way would be having the ScrollController as a state variable inside the ShellRoute widget. Which will act as a parent for the bottom routes. And reach the state of the shell route through context.findAncesterWidgetOfType or something like that. But you will have to make sure that the shell route is always there as a parent.
Comments have limits. But hopefully that can direct you to the right way.
I have posted a new question to continue this inquiry, as you have sufficiently answered the original question of how to get the CustomScrollViews to render.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.