Skip to content

Instantly share code, notes, and snippets.

@bizz84
Created March 17, 2022 10:17
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bizz84/911b984e30b16bee8cb090de98ab68f2 to your computer and use it in GitHub Desktop.
Save bizz84/911b984e30b16bee8cb090de98ab68f2 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'),
);
}
@hiteshgarg123
Copy link

Hi @bizz84, I was setting up BottomNavBar with go_router and found this example demonstrating the same, however, I found one issue, When I run this on the web, and I change the size of the browser window, the state of NavBar is getting reset to 1.
I assume it is happening due to the didUpdateWidget method. Do we have any solution for that?
Thanks

@ericf-br
Copy link

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.

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