Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save talamaska/a5ec2c64f2e5e305c5b111c61918fc87 to your computer and use it in GitHub Desktop.
Save talamaska/a5ec2c64f2e5e305c5b111c61918fc87 to your computer and use it in GitHub Desktop.
Example of GoRouter nested navigation using BottomNavigationBar
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(App());
class App extends StatelessWidget {
App({Key? key}) : super(key: key);
static const title = 'GoRouter Example: Nested Navigation';
@override
Widget build(BuildContext context) => MaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
title: title,
);
late final _router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/${Families.data[0].id}',
),
GoRoute(
path: '/:fid',
name: 'family',
builder: (context, state) => FamilyTabsScreen(
key: state.pageKey,
selectedFamily: Families.family(state.params['fid']!),
),
routes: [
GoRoute(
path: ':pid',
name: 'person',
builder: (context, state) {
final family = Families.family(state.params['fid']!);
final person = family.person(state.params['pid']!);
return PersonScreen(family: family, person: person);
},
),
],
),
],
// show the current router location as the user navigates page to page; note
// that this is not required for nested navigation but it is useful to show
// the location as it changes
navigatorBuilder: (context, state, child) => Material(
child: Column(
children: [
Expanded(child: child),
Padding(
padding: const EdgeInsets.all(8),
child: Text(state.location),
),
],
),
),
);
}
class FamilyTabsScreen extends StatefulWidget {
FamilyTabsScreen({required Family selectedFamily, Key? key})
: index = Families.data.indexWhere((f) => f.id == selectedFamily.id),
super(key: key) {
assert(index != -1);
}
final int index;
@override
_FamilyTabsScreenState createState() => _FamilyTabsScreenState();
}
class _FamilyTabsScreenState extends State<FamilyTabsScreen> {
@override
void didUpdateWidget(FamilyTabsScreen oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() => _selectedIndex = widget.index);
}
int _selectedIndex = 0;
void _tap(BuildContext context, int index) {
setState(() => _selectedIndex = index);
context.go('/${Families.data[index].id}');
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Use this if you want the share the same AppBar across all tabs
appBar: AppBar(title: Text(Families.data[_selectedIndex].name)),
body: FeedPage(
fid: Families.data[_selectedIndex].id,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.blue,
currentIndex: _selectedIndex,
items: [
for (final f in Families.data)
BottomNavigationBarItem(
icon: const Icon(
Icons.layers,
),
label: f.name,
),
],
onTap: (index) => _tap(context, index),
),
);
}
}
class FamilyView extends StatefulWidget {
const FamilyView({required this.family, Key? key}) : super(key: key);
final Family family;
@override
State<FamilyView> createState() => _FamilyViewState();
}
/// Use the [AutomaticKeepAliveClientMixin] to keep the state, like scroll
/// position and text fields when switching tabs, as well as when popping back
/// from sub screens. To use the mixin override [wantKeepAlive] and call
/// `super.build(context)` in build.
///
/// In this example if you make a web build and make the browser window so low
/// that you have to scroll to see the last person on each family tab, you will
/// see that state is kept when you switch tabs and when you open a person
/// screen and pop back to the family.
class _FamilyViewState extends State<FamilyView>
with AutomaticKeepAliveClientMixin {
// Override `wantKeepAlive` when using `AutomaticKeepAliveClientMixin`.
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
// Call `super.build` when using `AutomaticKeepAliveClientMixin`.
super.build(context);
return ListView(
children: [
for (final p in widget.family.people)
ListTile(
title: Text(p.name),
onTap: () => context.go('/${widget.family.id}/${p.id}'),
),
],
);
}
}
class PersonScreen extends StatelessWidget {
const PersonScreen({required this.family, required this.person, Key? key})
: super(key: key);
final Family family;
final Person person;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(person.name)),
body: Center(
child: Column(
children: [
Text('${person.name} ${family.name} is ${person.age} years old'),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.black, // background (button) color
onPrimary: Colors.white, // foreground (text) color
),
onPressed: () => context.goNamed('family', params: {
'fid': 'journal',
}),
child: const Text('Go to Journal'),
)
],
)),
);
}
class FeedPage extends StatelessWidget {
const FeedPage({Key? key, required this.fid}) : super(key: key);
final String fid;
@override
Widget build(BuildContext context) {
return Scaffold(
// Use this if you want each child page to have its own AppBar
//appBar: AppBar(title: const Text('Feed')),
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.black, // background (button) color
onPrimary: Colors.white, // foreground (text) color
),
onPressed: () => context.goNamed('person', params: {
'fid': fid,
'pid': 'p1',
}),
child: const Text('Detail'),
),
),
);
}
}
// DATA
class Person {
Person({required this.id, required this.name, required this.age});
final String id;
final String name;
final int age;
}
class Family {
Family({required this.id, required this.name, required this.people});
final String id;
final String name;
final List<Person> people;
Person person(String pid) => people.singleWhere(
(p) => p.id == pid,
orElse: () => throw Exception('unknown person $pid for family $id'),
);
}
class Families {
static final data = [
Family(
id: 'feed',
name: 'Sells',
people: [
Person(id: 'p1', name: 'Chris', age: 52),
Person(id: 'p2', name: 'John', age: 27),
Person(id: 'p3', name: 'Tom', age: 26),
],
),
Family(
id: 'journal',
name: 'Addams',
people: [
Person(id: 'p1', name: 'Gomez', age: 55),
Person(id: 'p2', name: 'Morticia', age: 50),
Person(id: 'p3', name: 'Pugsley', age: 10),
Person(id: 'p4', name: 'Wednesday', age: 17),
],
),
Family(
id: 'account',
name: 'Hunting',
people: [
Person(id: 'p1', name: 'Mom', age: 54),
Person(id: 'p2', name: 'Dad', age: 55),
Person(id: 'p3', name: 'Will', age: 20),
Person(id: 'p4', name: 'Marky', age: 21),
Person(id: 'p5', name: 'Ricky', age: 22),
Person(id: 'p6', name: 'Danny', age: 23),
Person(id: 'p7', name: 'Terry', age: 24),
Person(id: 'p8', name: 'Mikey', age: 25),
Person(id: 'p9', name: 'Davey', age: 26),
Person(id: 'p10', name: 'Timmy', age: 27),
Person(id: 'p11', name: 'Tommy', age: 28),
Person(id: 'p12', name: 'Joey', age: 29),
Person(id: 'p13', name: 'Robby', age: 30),
Person(id: 'p14', name: 'Johnny', age: 31),
Person(id: 'p15', name: 'Brian', age: 32),
],
),
];
static Family family(String fid) => data.family(fid);
}
extension on List<Family> {
Family family(String fid) => singleWhere(
(f) => f.id == fid,
orElse: () => throw Exception('unknown family $fid'),
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment