Skip to content

Instantly share code, notes, and snippets.

@tolo
Last active September 10, 2024 11:17
Show Gist options
  • Save tolo/0ee208acebc66ce76db7df9a2f270578 to your computer and use it in GitHub Desktop.
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
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