Skip to content

Instantly share code, notes, and snippets.

@yeasin50
Created November 4, 2026 17:38
Show Gist options
  • Select an option

  • Save yeasin50/d7fa04644155eb72ee5bde628ccd5415 to your computer and use it in GitHub Desktop.

Select an option

Save yeasin50/d7fa04644155eb72ee5bde628ccd5415 to your computer and use it in GitHub Desktop.
Question for HumdayQA: StatefulShellRoute.pageBuilder: failed to work with custom Page transitions
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
void main() => runApp(GoRouterIssueExample());
/// This has been created to share the issue in Flutter HumdayQA
///! https://www.youtube.com/watch?v=92MbzmqS_DQ
final _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: "root_nav");
//ISSUE: StatefulShellRoute.pageBuilder animation is not working
// My end desire is to it on top level so that I don't have to put on every `StatefulShellBranch`
// secondly I was thinking to change the index switch animation
final GoRouter _router = GoRouter(
initialLocation: "/a",
navigatorKey: _rootNavigatorKey,
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
pageBuilder: (context, state, shell) {
///! this does not work
return MyPage(
key: state.pageKey,
child: (context) => ScaffoldWithNavBar(
// some key issue??
key: ValueKey(state.uri.path),
child: shell,
),
);
// But this works fine
return CustomTransitionPage(
child: ScaffoldWithNavBar(child: shell),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset(1, 0),
end: .zero,
).animate(animation).value,
child: child,
),
);
},
);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: "/a",
builder: (context, state) => ScreenA(),
routes: [
GoRoute(
path: "details",
pageBuilder: (BuildContext context, GoRouterState state) {
return MyPage(
key: state.pageKey,
child: (context) => DetailsScreen(label: "A"),
);
},
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: "/b",
builder: (context, state) => ScreenB(),
routes: [
GoRoute(
path: "details",
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => DetailsScreen(label: "B"),
),
],
),
],
),
],
),
],
);
class MyPage<T> extends Page<T> {
const new({super.key, required this.child});
final WidgetBuilder child;
@override
Route<T> createRoute(BuildContext context) {
return MyPageRoute<T>(
builder: child,
duration: Durations.medium1,
settings: this,
);
}
}
class MyPageRoute<T> extends PageRoute<T> {
new({
super.settings,
super.requestFocus,
super.traversalEdgeBehavior,
super.directionalTraversalEdgeBehavior,
super.fullscreenDialog,
super.allowSnapshotting,
super.barrierDismissible,
required this.builder,
required this.duration,
});
final WidgetBuilder builder;
final Duration duration;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset(1, 0),
end: .zero,
).animate(animation).value,
child: child,
),
);
}
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => builder(context);
@override
bool get maintainState => true;
@override
Duration get transitionDuration => duration;
}
class GoRouterIssueExample extends StatelessWidget {
const GoRouterIssueExample({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter StatefulNavigationShell route',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
routerConfig: _router,
);
}
}
class ScaffoldWithNavBar extends StatelessWidget {
const ScaffoldWithNavBar({required this.child, super.key});
final StatefulNavigationShell child;
@override
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'A Screen'),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'B Screen',
),
],
currentIndex: _calculateSelectedIndex(context),
onTap: (int index) {
child.goBranch(index);
},
),
);
}
int _calculateSelectedIndex(BuildContext context) {
final routePath = GoRouterState.of(context).uri.path;
if (routePath.startsWith("/a")) return 0;
if (routePath.startsWith("/b")) return 1;
return 0;
}
}
class ScreenA extends StatefulWidget {
const ScreenA({super.key});
@override
State<ScreenA> createState() => _ScreenAState();
}
class _ScreenAState extends State<ScreenA> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.red.shade100,
body: Column(
mainAxisAlignment: .center,
crossAxisAlignment: .stretch,
spacing: 24,
children: <Widget>[
InkWell(
onTap: () {
counter++;
setState(() {});
},
child: Text(
'Screen A\n $counter',
textAlign: .center,
style: .new(fontSize: 45),
),
),
TextButton(
onPressed: () {
GoRouter.of(context).go('/a/details');
},
child: const Text('View A details'),
),
],
),
);
}
}
class ScreenB extends StatefulWidget {
const ScreenB({super.key});
@override
State<ScreenB> createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.greenAccent.shade100,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: .stretch,
spacing: 24,
children: <Widget>[
InkWell(
onTap: () {
counter++;
setState(() {});
},
child: Text(
'Screen B\n $counter',
textAlign: .center,
style: .new(fontSize: 45),
),
),
TextButton(
onPressed: () {
GoRouter.of(context).go('/b/details');
},
child: const Text('View B details'),
),
],
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
const DetailsScreen({required this.label, super.key});
final String label;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: Text(
'Details for $label',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment