-
-
Save yeasin50/d7fa04644155eb72ee5bde628ccd5415 to your computer and use it in GitHub Desktop.
Question for HumdayQA: StatefulShellRoute.pageBuilder: failed to work with custom Page transitions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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