Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to work with NavigationBar in go_router? | Flutter

I am currently struggling refactoring my routing code with go_router.

I already got some simple routes like /signin & /signup, but the problem comes in when I try to make the routing work with a BottomNavigationBar that has multiple screens. I would like to have a separate route for each of them like /home, /events & /profile.

I figured out that I somehow have to return the same widget with a different parameter to prevent the whole screen to change whenever a BottomNavigationBarItem is pressed and instead only update the part above the BottomNavigationBar which would be the screen itself.

I came up with a pretty tricky solution:

GoRoute(
  path: '/:path',
  builder: (BuildContext context, GoRouterState state) {
    final String path = state.params['path']!;

    if (path == 'signin') {
      return const SignInScreen();
    }

    if (path == 'signup') {
      return const SignUpScreen();
    }

    if (path == 'forgot-password') {
      return const ForgotPasswordScreen();
    }

    // Otherwise it has to be the ScreenWithBottomBar

    final int index = getIndexFromPath(path);

    if (index != -1) {
      return MainScreen(selectedIndex: index);
    }

    return const ErrorScreen();
  }
)

This does not look very good and it makes it impossible to add subroutes like /profile/settings/appearance or /events/:id.

I would like to have something easy understandable like this:

GoRoute(
  path: '/signin',
  builder: (BuildContext context, GoRouterState state) {
    return const SignInScreen();
  }
),
GoRoute(
  path: '/signup',
  builder: (BuildContext context, GoRouterState state) {
    return const SignUpScreen();
  }
),
GoRoute(
  path: '/home',
  builder: (BuildContext context, GoRouterState state) {
    return const ScreenWithNavBar(selectedScreen: 1);
  }
),
GoRoute(
  path: '/events',
  builder: (BuildContext context, GoRouterState state) {
    return const ScreenWithNavBar(selectedScreen: 2);
  },
  routes: <GoRoute>[
    GoRoute(
      path: ':id',
      builder: (BuildContext context, GoRouterState state) {
        return const EventScreen();
      }
    )
  ]
)

Is there any way to achieve the behavior?

like image 701
Florian Leeser Avatar asked Sep 10 '25 17:09

Florian Leeser


1 Answers

Now you can use ShellRouter with GoRouter to create Navigation Bar


Explaination:

Things to keep in mind while using context.go() from ShellRoute to GoRoute

  1. Specify parentNavigatorKey prop in each GoRoute
  2. Use context.go() to replace page , context.push() to push page to stack

Code Structure to follow:


final _parentKey = GlobalKey<NavigatorState>();
final _shellKey = GlobalKey<NavigatorState>();

|_ GoRoute
  |_ parentNavigatorKey = _parentKey   👈 Specify key here
|_ ShellRoute
  |_ GoRoute                            // Needs Bottom Navigation                  
    |_ parentNavigatorKey = _shellKey   
  |_ GoRoute                            // Needs Bottom Navigation
    |_ parentNavigatorKey = _shellKey   
|_ GoRoute                              // Full Screen which doesn't need Bottom Navigation
  |_parentNavigatorKey = _parentKey

Code:

Router


final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();

final router = GoRouter(
  initialLocation: '/',
  navigatorKey: _rootNavigatorKey,
  routes: [
    ShellRoute(
      navigatorKey: _shellNavigatorKey,
      pageBuilder: (context, state, child) {
        print(state.location);
        return NoTransitionPage(
            child: ScaffoldWithNavBar(
          location: state.location,
          child: child,
        ));
      },
      routes: [
        GoRoute(
          path: '/',
          parentNavigatorKey: _shellNavigatorKey,
          pageBuilder: (context, state) {
            return const NoTransitionPage(
              child: Scaffold(
                body: Center(child: Text("Home")),
              ),
            );
          },
        ),
        GoRoute(
          path: '/discover',
          parentNavigatorKey: _shellNavigatorKey,
          pageBuilder: (context, state) {
            return const NoTransitionPage(
              child: Scaffold(
                body: Center(child: Text("Discover")),
              ),
            );
          },
        ),
        GoRoute(
            parentNavigatorKey: _shellNavigatorKey,
            path: '/shop',
            pageBuilder: (context, state) {
              return const NoTransitionPage(
                child: Scaffold(
                  body: Center(child: Text("Shop")),
                ),
              );
            }),
      ],
    ),
    GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      path: '/login',
      pageBuilder: (context, state) {
        return NoTransitionPage(
          key: UniqueKey(),
          child: Scaffold(
            appBar: AppBar(),
            body: const Center(
              child: Text("Login"),
            ),
          ),
        );
      },
    ),
  ],
);

BottomNavigationBar

class ScaffoldWithNavBar extends StatefulWidget {
  String location;
  ScaffoldWithNavBar({super.key, required this.child, required this.location});

  final Widget child;

  @override
  State<ScaffoldWithNavBar> createState() => _ScaffoldWithNavBarState();
}

class _ScaffoldWithNavBarState extends State<ScaffoldWithNavBar> {
  int _currentIndex = 0;

  static const List<MyCustomBottomNavBarItem> tabs = [
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.home),
      activeIcon: Icon(Icons.home),
      label: 'HOME',
      initialLocation: '/',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.explore_outlined),
      activeIcon: Icon(Icons.explore),
      label: 'DISCOVER',
      initialLocation: '/discover',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.storefront_outlined),
      activeIcon: Icon(Icons.storefront),
      label: 'SHOP',
      initialLocation: '/shop',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.account_circle_outlined),
      activeIcon: Icon(Icons.account_circle),
      label: 'MY',
      initialLocation: '/login',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    const labelStyle = TextStyle(fontFamily: 'Roboto');
    return Scaffold(
      body: SafeArea(child: widget.child),
      bottomNavigationBar: BottomNavigationBar(
        selectedLabelStyle: labelStyle,
        unselectedLabelStyle: labelStyle,
        selectedItemColor: const Color(0xFF434343),
        selectedFontSize: 12,
        unselectedItemColor: const Color(0xFF838383),
        showUnselectedLabels: true,
        type: BottomNavigationBarType.fixed,
        onTap: (int index) {
          _goOtherTab(context, index);
        },
        currentIndex: widget.location == '/'
            ? 0
            : widget.location == '/discover'
                ? 1
                : widget.location == '/shop'
                    ? 2
                    : 3,
        items: tabs,
      ),
    );
  }

  void _goOtherTab(BuildContext context, int index) {
    if (index == _currentIndex) return;
    GoRouter router = GoRouter.of(context);
    String location = tabs[index].initialLocation;

    setState(() {
      _currentIndex = index;
    });
    if (index == 3) {
      context.push('/login');
    } else {
      router.go(location);
    }
  }
}

class MyCustomBottomNavBarItem extends BottomNavigationBarItem {
  final String initialLocation;

  const MyCustomBottomNavBarItem(
      {required this.initialLocation,
      required Widget icon,
      String? label,
      Widget? activeIcon})
      : super(icon: icon, label: label, activeIcon: activeIcon ?? icon);
}

Output:

enter image description here

enter image description here

like image 125
krishnaacharyaa Avatar answered Sep 13 '25 07:09

krishnaacharyaa