Instantly share code, notes, and snippets.
Created
March 17, 2022 10:17
-
Star
(8)
8
You must be signed in to star a gist -
Fork
(1)
1
You must be signed in to fork a gist
-
Save bizz84/911b984e30b16bee8cb090de98ab68f2 to your computer and use it in GitHub Desktop.
Example of GoRouter nested navigation using BottomNavigationBar
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'; | |
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
I'll throw a huge wrench in... (which is what I've struggled with using GoRouter).
Now make it to where clicking the Detail button doesn't remove the tab bar from the screen.