Last active
September 10, 2024 11:17
-
-
Save tolo/0ee208acebc66ce76db7df9a2f270578 to your computer and use it in GitHub Desktop.
Demo of using StatefulShellRoute in go_router with modal full screen routes and deep linking
This file contains 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:flutter/material.dart'; | |
import 'package:go_router/go_router.dart'; | |
final GlobalKey<NavigatorState> _rootNavigatorKey = | |
GlobalKey<NavigatorState>(debugLabel: 'root'); | |
void main() { | |
runApp(NestedTabNavigationExampleApp()); | |
} | |
/// An example demonstrating how to use nested navigation with modal routs and | |
/// deep links ('/app/notifications'). | |
class NestedTabNavigationExampleApp extends StatelessWidget { | |
NestedTabNavigationExampleApp({super.key}); | |
late final _tabbedShellRoute = StatefulShellRoute.indexedStack( | |
builder: (context, state, navigationShell) { | |
return ScaffoldWithNavBar(navigationShell: navigationShell); | |
}, | |
branches: [ | |
StatefulShellBranch( | |
routes: [ | |
GoRoute( | |
path: '/app/tab1', | |
name: 'tab1', | |
builder: (context, state) { | |
return ExamplePage( | |
title: 'tab1', | |
subPagePath: '/app/tab1/detail', | |
deepLinkId: state.uri.queryParameters['deeplink']); | |
}, | |
routes: [ | |
GoRoute( | |
path: 'detail', | |
name: 'tab1detail', | |
builder: (context, state) => | |
const ExamplePage(title: 'tab1 - detail'), | |
), | |
]), | |
], | |
), | |
StatefulShellBranch( | |
routes: [ | |
GoRoute( | |
path: '/app/tab2', | |
name: 'tab2', | |
builder: (context, state) { | |
return ExamplePage( | |
title: 'tab2', | |
subPagePath: '/app/tab2/detail', | |
deepLinkId: state.uri.queryParameters['deeplink']); | |
}, | |
routes: [ | |
GoRoute( | |
path: 'detail', | |
name: 'tab2detail', | |
builder: (context, state) => | |
const ExamplePage(title: 'tab2 - detail'), | |
), | |
]), | |
], | |
), | |
], | |
); | |
late final _router = GoRouter( | |
navigatorKey: _rootNavigatorKey, | |
initialLocation: '/login', | |
routes: [ | |
GoRoute( | |
path: '/login', | |
name: 'login', | |
builder: (context, state) => const LoginPage(), | |
), | |
_tabbedShellRoute, | |
GoRoute( | |
path: '/app/notifications', | |
name: 'notifications', | |
parentNavigatorKey: _rootNavigatorKey, | |
builder: (context, state) => const NotificationsPage(), | |
), | |
], | |
redirect: (BuildContext context, GoRouterState state) { | |
if (state.matchedLocation == '/app/deeplink') { | |
return '/app/tab1?deeplink=${DateTime.now().millisecondsSinceEpoch}'; | |
} | |
return null; | |
}, | |
); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp.router( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
routerConfig: _router, | |
); | |
} | |
} | |
class ScaffoldWithNavBar extends StatelessWidget { | |
const ScaffoldWithNavBar({ | |
required this.navigationShell, | |
Key? key, | |
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar')); | |
final StatefulNavigationShell navigationShell; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: navigationShell, | |
bottomNavigationBar: BottomNavigationBar( | |
items: const <BottomNavigationBarItem>[ | |
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'), | |
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'), | |
], | |
currentIndex: navigationShell.currentIndex, | |
onTap: (int index) => navigationShell.goBranch(index), | |
), | |
); | |
} | |
} | |
class LoginPage extends StatelessWidget { | |
const LoginPage({ | |
super.key, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: <Widget>[ | |
Text('Login Page'), | |
SizedBox.square(dimension: 50), | |
ElevatedButton( | |
onPressed: () { | |
GoRouter.of(context).go('/app/tab1'); | |
}, | |
child: Text('Login')), | |
], | |
), | |
), | |
); | |
} | |
} | |
class ExamplePage extends StatefulWidget { | |
const ExamplePage({ | |
required this.title, | |
this.subPagePath, | |
this.deepLinkId, | |
super.key, | |
}); | |
final String title; | |
final String? subPagePath; | |
final String? deepLinkId; | |
@override | |
State<StatefulWidget> createState() => _ExamplePageState(); | |
} | |
class _ExamplePageState extends State<ExamplePage> { | |
String? _lastDeepLinkId; | |
void _checkDeepLink() { | |
final routeState = GoRouterState.of(context); | |
if (widget.deepLinkId != _lastDeepLinkId && | |
routeState.topRoute?.name == 'tab1') { | |
_lastDeepLinkId = widget.deepLinkId; | |
GoRouter.of(context).push('/app/notifications'); | |
} | |
} | |
@override | |
void initState() { | |
super.initState(); | |
WidgetsBinding.instance.addPostFrameCallback((_) { | |
_checkDeepLink(); | |
}); | |
} | |
@override | |
void didUpdateWidget(covariant ExamplePage oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
_checkDeepLink(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
actions: [ | |
IconButton( | |
icon: const Icon(Icons.notifications), | |
onPressed: () { | |
GoRouter.of(context).push('/app/notifications'); | |
}, | |
), | |
], | |
), | |
body: Center( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: <Widget>[ | |
Text('This is the \'${widget.title}\' screen.'), | |
if (widget.subPagePath != null) ...[ | |
SizedBox.square(dimension: 50), | |
ElevatedButton( | |
onPressed: () { | |
GoRouter.of(context).go(widget.subPagePath!); | |
}, | |
child: Text('Go to detail')), | |
], | |
], | |
), | |
), | |
); | |
} | |
} | |
class NotificationsPage extends StatelessWidget { | |
const NotificationsPage({ | |
super.key, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text('Notificatons'), | |
leading: BackButton(onPressed: () { | |
GoRouter.of(context).pop(); | |
}), | |
), | |
body: Center( | |
child: ListView( | |
children: <Widget>[ | |
ListTile( | |
title: Text('Notification 1'), | |
), | |
ListTile( | |
title: Text('Notification 2'), | |
), | |
ListTile( | |
title: Text('Notification 3'), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment