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