Is it possible to implement Navigation bar using GoRouter Package?

428

Yes, it is possible.

Let's use the example in GoRouter documentation as a starting point.

  1. We need to create some basic models to store data:
/// Just a generic model that will be used to present some data on the screen.
class Person {
  final String id;
  final String name;

  Person({required this.id, required this.name});
}

/// Family will be the model that represents our tabs. We use the properties `icon` and `name` in the `NavigationBar`.
class Family {
  final String id;
  final String name;
  final List<Person> people;
  final Icon icon;

  Family({
    required this.id,
    required this.name,
    required this.people,
    required this.icon,
  });
}

/// Families will be used to store the tabs to be easily accessed anywhere. In a real application you would use something fancier.
class Families {
  static const List<Icon> icons = [
    Icon(Icons.looks_one),
    Icon(Icons.looks_two),
    Icon(Icons.looks_3)
  ];

  static final List<Family> data = List.generate(
    3,
    (fid) => Family(
      id: '$fid',
      name: 'Family $fid',
      people: List.generate(
        10,
        (id) => Person(id: '$id', name: 'Family $fid Person $id'),
      ),
      icon: icons[fid],
    ),
  );
}
  1. Now we'll create the basic views that will render the model's data:
/// Used to present Person's data.
class PersonView extends StatelessWidget {
  const PersonView({required this.person, Key? key}) : super(key: key);
  final Person person;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text(person.name),
      ),
    );
  }
}

/// This is the view that will be used by each application's tab.
class FamilyView extends StatefulWidget {
  const FamilyView({required this.family, Key? key}) : super(key: key);
  final Family family;

  @override
  State<FamilyView> createState() => _FamilyViewState();
}


class _FamilyViewState extends State<FamilyView>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      children: [
        for (final p in widget.family.people)
          ListTile(
            title: Text(p.name),
            onTap: () =>
                context.go('/family/${widget.family.id}/person/${p.id}'),
          ),
      ],
    );
  }
}

  1. Until now we did nothing different compared to the GoRouter documentation, so let's finally create the widget that will show the NavigationBar:
class FamilyTabsScreen extends StatefulWidget {
  final int index;
  FamilyTabsScreen({required Family currentFamily, Key? key})
      : index = Families.data.indexWhere((f) => f.id == currentFamily.id),
        super(key: key) {
    assert(index != -1);
  }

  @override
  _FamilyTabsScreenState createState() => _FamilyTabsScreenState();
}

class _FamilyTabsScreenState extends State<FamilyTabsScreen>
    with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text(_title(context)),
        ),
        body: FamilyView(family: Families.data[widget.index]),
        bottomNavigationBar: NavigationBar(
          destinations: [
            for (final f in Families.data)
              NavigationDestination(
                icon: f.icon,
                label: f.name,
              )
          ],
          onDestinationSelected: (index) => _tap(context, index),
          selectedIndex: widget.index,
        ),
      );

  void _tap(BuildContext context, int index) =>
      context.go('/family/${Families.data[index].id}');

  String _title(BuildContext context) =>
      (context as Element).findAncestorWidgetOfExactType<MaterialApp>()!.title;
}

This is the important part of the code above:

/// [...] suppressed code
bottomNavigationBar: NavigationBar(
  destinations: [
    for (final f in Families.data)
      NavigationDestination(
        icon: f.icon,
        label: f.name,
      )
  ],
  onDestinationSelected: (index) => _tap(context, index),
  selectedIndex: widget.index,
),
/// [...] suppressed code

So, basically we are using the NavigationBar almost exactly as we would use the TabBarView.

  1. Finally, this will only work if we define the app's routes and set the GoRouter as the app's navigator:

void main() {
  GoRouter.setUrlPathStrategy(UrlPathStrategy.path);
  runApp(const MyApp());
}

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      redirect: (_) => '/family/${Families.data[0].id}',
    ),
    GoRoute(
        path: '/family/:fid',
        builder: (context, state) {
          final fid = state.params['fid']!;
          final family = Families.data.firstWhere((f) => f.id == fid,
              orElse: () => throw Exception('family not found: $fid'));

          return FamilyTabsScreen(key: state.pageKey, currentFamily: family);
        },
        routes: [
          GoRoute(
            path: 'person/:id',
            builder: (context, state) {
              final fid = state.params['fid']!;
              final id = state.params['id'];

              final person = Families.data
                  .firstWhere((f) => f.id == fid,
                      orElse: () => throw Exception('family not found: $fid'))
                  .people
                  .firstWhere(
                    ((p) => p.id == id),
                    orElse: () => throw Exception('person not found: $id'),
                  );

              return PersonView(key: state.pageKey, person: person);
            },
          ),
        ]),
  ],
);

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      routeInformationParser: _router.routeInformationParser,
      routerDelegate: _router.routerDelegate,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

With all these steps you will have this:

enter image description here

Share:
428
Admin
Author by

Admin

Updated on November 28, 2022

Comments

  • Admin
    Admin over 1 year

    I am trying to implement a NavigationBar using the new Material You API.

    https://api.flutter.dev/flutter/material/NavigationBar-class.html

    I was just curious to know if we can implement the same using the Go_Router package .

  • Admin
    Admin over 2 years
    Thanks for the above but i would like further clarify if its possible to persist the bottom navigation bar after navigating to the new page.
  • Eduardo Vital
    Eduardo Vital over 2 years
    What do you mean by persisting? Does it mean keep showing the tab bar on every page? Or does it mean keeping the state of the screen (scrolling position by example) when you go back from the new page to the tab bar screen?
  • Admin
    Admin over 2 years
    Sorry if i was not clear. I meant to keep showing the tab bar on every page after navigation.
  • tazboy
    tazboy almost 2 years
    I really wish I could decode this. It's what I need, but I need it simpler.