Skip to content

Instantly share code, notes, and snippets.

@onatcipli
Last active May 24, 2024 03:49
Show Gist options
  • Save onatcipli/aed0372c987b4ae32311fe32bb4c1209 to your computer and use it in GitHub Desktop.
Save onatcipli/aed0372c987b4ae32311fe32bb4c1209 to your computer and use it in GitHub Desktop.
go_router_example.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
main() {
CustomNavigationHelper.instance;
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: CustomNavigationHelper.router,
);
}
}
class CustomNavigationHelper {
static final CustomNavigationHelper _instance =
CustomNavigationHelper._internal();
static CustomNavigationHelper get instance => _instance;
static late final GoRouter router;
static final GlobalKey<NavigatorState> parentNavigatorKey =
GlobalKey<NavigatorState>();
static final GlobalKey<NavigatorState> homeTabNavigatorKey =
GlobalKey<NavigatorState>();
static final GlobalKey<NavigatorState> searchTabNavigatorKey =
GlobalKey<NavigatorState>();
static final GlobalKey<NavigatorState> settingsTabNavigatorKey =
GlobalKey<NavigatorState>();
BuildContext get context =>
router.routerDelegate.navigatorKey.currentContext!;
GoRouterDelegate get routerDelegate => router.routerDelegate;
GoRouteInformationParser get routeInformationParser =>
router.routeInformationParser;
static const String signUpPath = '/signUp';
static const String signInPath = '/signIn';
static const String detailPath = '/detail';
static const String rootDetailPath = '/rootDetail';
static const String homePath = '/home';
static const String settingsPath = '/settings';
static const String searchPath = '/search';
factory CustomNavigationHelper() {
return _instance;
}
CustomNavigationHelper._internal() {
final routes = [
StatefulShellRoute.indexedStack(
parentNavigatorKey: parentNavigatorKey,
branches: [
StatefulShellBranch(
navigatorKey: homeTabNavigatorKey,
routes: [
GoRoute(
path: homePath,
pageBuilder: (context, GoRouterState state) {
return getPage(
child: const HomePage(),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: searchTabNavigatorKey,
routes: [
GoRoute(
path: searchPath,
pageBuilder: (context, state) {
return getPage(
child: const SearchPage(),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: settingsTabNavigatorKey,
routes: [
GoRoute(
path: settingsPath,
pageBuilder: (context, state) {
return getPage(
child: const SettingsPage(),
state: state,
);
},
),
],
),
],
pageBuilder: (
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
return getPage(
child: BottomNavigationPage(
child: navigationShell,
),
state: state,
);
},
),
GoRoute(
parentNavigatorKey: parentNavigatorKey,
path: signUpPath,
pageBuilder: (context, state) {
return getPage(
child: const SignUpPage(),
state: state,
);
},
),
GoRoute(
parentNavigatorKey: parentNavigatorKey,
path: signInPath,
pageBuilder: (context, state) {
return getPage(
child: const SignInPage(),
state: state,
);
},
),
GoRoute(
path: detailPath,
pageBuilder: (context, state) {
return getPage(
child: const DetailPage(),
state: state,
);
},
),
GoRoute(
parentNavigatorKey: parentNavigatorKey,
path: rootDetailPath,
pageBuilder: (context, state) {
return getPage(
child: const DetailPage(),
state: state,
);
},
),
];
router = GoRouter(
navigatorKey: parentNavigatorKey,
initialLocation: signUpPath,
routes: routes,
);
}
static Page getPage({
required Widget child,
required GoRouterState state,
}) {
return MaterialPage(
key: state.pageKey,
child: child,
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.push(
CustomNavigationHelper.detailPath,
);
},
child: const Text('Push Detail'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Using push method of router enable us to navigate in the current tab without losing the shell',
textAlign: TextAlign.center,
),
),
/// TODO continue
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.push(
CustomNavigationHelper.rootDetailPath,
);
},
child: const Text('Push Detail From Root Navigator'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Using push method of router enable us to navigate in the current tab without losing the shell',
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Detail"),
),
);
}
}
class SignUpPage extends StatelessWidget {
const SignUpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SignUp"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.push(
CustomNavigationHelper.signInPath,
);
},
child: const Text('Push SignIn'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Using push method of router enable us to go back functionality',
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
class SignInPage extends StatelessWidget {
const SignInPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SignIn"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.push(
CustomNavigationHelper.homePath,
);
},
child: const Text('Push Home Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Using push method of router enable us to push that page as standalone page instead of showing with Shell',
textAlign: TextAlign.center,
),
),
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.go(
CustomNavigationHelper.homePath,
);
},
child: const Text('Go Home Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Instead if we use go method of router we will have the home page with the Shell',
textAlign: TextAlign.center,
),
),
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.go(
CustomNavigationHelper.searchPath,
);
},
child: const Text('Go Search Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Or instead we can launch the bottom navigation page(with shell) for different tab with only changing the path',
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Search"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.go(
CustomNavigationHelper.homePath
);
CustomNavigationHelper.router.push(
CustomNavigationHelper.detailPath
);
},
child: const Text('Go Home Tab -> Push Detail Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'It will change the tab without loosing the state',
textAlign: TextAlign.center,
),
),
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.go(
CustomNavigationHelper.settingsPath,
);
},
child: const Text('Go Settings Tab'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Or instead we can launch the bottom navigation page(with shell) for different tab with only changing the path',
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Settings"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.go(
CustomNavigationHelper.signInPath,
);
},
child: const Text('Go SignIn Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Or instead we can launch the bottom navigation page(with shell) for different tab with only changing the path',
textAlign: TextAlign.center,
),
),
ElevatedButton(
onPressed: () {
CustomNavigationHelper.router.push(
CustomNavigationHelper.signUpPath,
);
},
child: const Text('Push SignIn Page'),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Or instead we can launch the bottom navigation page(with shell) for different tab with only changing the path',
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
class BottomNavigationPage extends StatefulWidget {
const BottomNavigationPage({
super.key,
required this.child,
});
final StatefulNavigationShell child;
@override
State<BottomNavigationPage> createState() => _BottomNavigationPageState();
}
class _BottomNavigationPageState extends State<BottomNavigationPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bottom Navigator Shell'),
),
body: SafeArea(
child: widget.child,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: widget.child.currentIndex,
onTap: (index) {
widget.child.goBranch(
index,
initialLocation: index == widget.child.currentIndex,
);
setState(() {});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'search',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'settings',
),
],
),
);
}
}
@UsamaKarim
Copy link

UsamaKarim commented Sep 20, 2023

It could have been better if the file was named with .dart extension.

@onatcipli
Copy link
Author

thanks for the warning 🙌 @UsamaKarim

@dimeprog
Copy link

Thanks

@simmi9
Copy link

simmi9 commented Oct 20, 2023

i wonder if there will be a cyclic dependency if I create the helper as a seperate file in util directory. We will have to import helper in pages and pages in helper file then Is there a way to avoid that?

@onatcipli
Copy link
Author

Hey @simmi9, I think its not a problem in dart maybe following SO answers could help you;

https://stackoverflow.com/a/67039376/10685785
https://stackoverflow.com/a/70156273/10685785

@mohit-mobikul
Copy link

Hi, How do we rebuild a tab bar page once it is rendered when we click on it the first time?

@fabioafreitas
Copy link

this helped me a lot, thanks

@hungvu193
Copy link

This's exactly what I need! Thank you man! 🔥

@renal31
Copy link

renal31 commented Dec 31, 2023

@onatcipli Thanks a lot for this very useful sample.

Still there is a flaw here in my opinion (and from my understanding):
The association between BottomNavigationBarItem and StatefulShellBranch is very weak (indexed based).
So, if you don't declare these two collections in the exact same order it won't work as expected.

Isn't there a way to use the StatefulShellBranch key in BottomNavigationBarItem declaration for stronger linking ?

@RNOVOSELOV
Copy link

RNOVOSELOV commented Jan 10, 2024

Good day, thks for you work, what version go_router does you use?

@harshitsaini98
Copy link

Hi, How do we rebuild a tab bar page once it is rendered when we click on it the first time?

Hi, did you get any solution for this? I am also facing a similar problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment