Skip to content

Instantly share code, notes, and snippets.

@splex7
Last active November 3, 2022 07:07
Show Gist options
  • Save splex7/bfe86b3b034c1e8f3e4bc92e9b270532 to your computer and use it in GitHub Desktop.
Save splex7/bfe86b3b034c1e8f3e4bc92e9b270532 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
GoRouter.setUrlPathStrategy(UrlPathStrategy.path);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
// private navigators
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
final tabs = [
const ScaffoldWithNavBarTabItem(
initialLocation: '/a',
icon: Icon(Icons.home),
label: 'Section A',
),
const ScaffoldWithNavBarTabItem(
initialLocation: '/b',
icon: Icon(Icons.settings),
label: 'Section B',
),
];
final goRouter = GoRouter(
initialLocation: '/a',
navigatorKey: _rootNavigatorKey,
debugLogDiagnostics: true,
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (context, state, child) {
return ScaffoldWithBottomNavBar(tabs: tabs, child: child);
},
routes: [
// Products
GoRoute(
path: '/a',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const RootScreen(label: 'A', detailsPath: '/a/details'),
),
routes: [
GoRoute(
path: 'details',
builder: (context, state) => const DetailsScreen(label: 'A'),
),
],
),
// Shopping Cart
GoRoute(
path: '/b',
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const RootScreen(label: 'B', detailsPath: '/b/details'),
),
routes: [
GoRoute(
path: 'details',
builder: (context, state) => const DetailsScreen(label: 'B'),
),
],
),
],
),
],
);
return MaterialApp.router(
routerConfig: goRouter,
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.indigo),
);
}
}
/// Representation of a tab item in a [ScaffoldWithNavBar]
class ScaffoldWithNavBarTabItem extends BottomNavigationBarItem {
/// Constructs an [ScaffoldWithNavBarTabItem].
const ScaffoldWithNavBarTabItem(
{required this.initialLocation, required Widget icon, String? label})
: super(icon: icon, label: label);
/// The initial location/path
final String initialLocation;
}
class ScaffoldWithBottomNavBar extends StatefulWidget {
const ScaffoldWithBottomNavBar(
{Key? key, required this.child, required this.tabs})
: super(key: key);
final Widget child;
final List<ScaffoldWithNavBarTabItem> tabs;
@override
State<ScaffoldWithBottomNavBar> createState() =>
_ScaffoldWithBottomNavBarState();
}
class _ScaffoldWithBottomNavBarState extends State<ScaffoldWithBottomNavBar> {
int _locationToTabIndex(String location) {
final index =
widget.tabs.indexWhere((t) => location.startsWith(t.initialLocation));
// if index not found (-1), return 0
return index < 0 ? 0 : index;
}
int get _currentIndex => _locationToTabIndex(GoRouter.of(context).location);
void _onItemTapped(BuildContext context, int tabIndex) {
// Only navigate if the tab index has changed
if (tabIndex != _currentIndex) {
context.go(widget.tabs[tabIndex].initialLocation);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: widget.child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: widget.tabs,
onTap: (index) => _onItemTapped(context, index),
),
);
}
}
/// Widget for the root/initial pages in the bottom navigation bar.
class RootScreen extends StatelessWidget {
/// Creates a RootScreen
const RootScreen({required this.label, required this.detailsPath, Key? key})
: super(key: key);
/// The label
final String label;
/// The path to the detail page
final String detailsPath;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tab root - $label'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Screen $label',
style: Theme.of(context).textTheme.titleLarge),
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () => context.go(detailsPath),
child: const Text('View details'),
),
],
),
),
);
}
}
/// The details screen for either the A or B screen.
class DetailsScreen extends StatefulWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
Key? key,
}) : super(key: key);
/// The label to display in the center of the screen.
final String label;
@override
State<StatefulWidget> createState() => DetailsScreenState();
}
/// The state for DetailsScreen
class DetailsScreenState extends State<DetailsScreen> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details Screen - ${widget.label}'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Details for ${widget.label} - Counter: $_counter',
style: Theme.of(context).textTheme.titleLarge),
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () {
setState(() {
_counter++;
});
},
child: const Text('Increment counter'),
),
],
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment