0

I'm using Go Router 6.2.0 with Flutter 3.7.3

Problem: I want to navigate to the shell route when a user is logged in and the login page when no one is logged in. What developers usually do is redirect the route to another one when user is logged in but I can't redirect the route to a shell route because I don't know how

I have the 2 routes in my GoRouter, 1 is the Login route for the login page, in my login page I'm doing this to check for auth then go to the another route accordingly:

  Scaffold(
      body: StreamBuilder(
          stream: FirebaseAuth.instance.authStateChanges(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.active) {
              final user = snapshot.data;
              if (user != null) {
                GoRouter.of(context).go(NavigationRailScreen.routeName);
              }
            }
            return Center(
              child: SizedBox(
                width: 400,
                child: ui.SignInScreen(
                  providers: [ui.EmailAuthProvider()],
                  actions: [
                    ui.AuthStateChangeAction<ui.SignedIn>((context, state) {
                      GoRouter.of(context).go(NavigationRailScreen.routeName);
                    }),
                  ],
                ),
              ),
            );
          }),
    );

The second route is a NavigationRail which has all the rest of my routes

final GlobalKey<NavigatorState> _rootNavigator = GlobalKey(debugLabel: 'root');
final GlobalKey<NavigatorState> _shellNavigator =
    GlobalKey(debugLabel: 'shell');

GoRouter router = GoRouter(
  navigatorKey: _rootNavigator,
  initialLocation: '/',
  routes: [
    GoRoute(
      parentNavigatorKey: _rootNavigator,
      path: '/',
      name: 'login',
      builder: (context, state) => AppLoginScreen(key: state.pageKey),
    ),
    ShellRoute(
        navigatorKey: _shellNavigator,
        builder: (context, state, child) =>
            NavigationRailScreen(key: state.pageKey, child: child),
        routes: [
          GoRoute(
            path: '/',
            name: HomeScreen.routeName,
            pageBuilder: (context, state) {
              return NoTransitionPage(child: HomeScreen(key: state.pageKey));
            },
          ),
          GoRoute(
              path: '/restaurants',
              name: RestaurantsScreen.routeName,
              pageBuilder: (context, state) {
                return NoTransitionPage(
                    child: RestaurantsScreen(key: state.pageKey));
              },
              routes: [
                GoRoute(
                  parentNavigatorKey: _shellNavigator,
                  path: ':id',
                  name: RestaurantDetailsScreen.routeName,
                  pageBuilder: (context, state) {
                    final String id = state.params['id'] as String;
                    return NoTransitionPage(
                      child:
                          RestaurantDetailsScreen(id: id, key: state.pageKey),
                    );
                  },
                  routes: [
                    GoRoute(
                      parentNavigatorKey: _shellNavigator,
                      path: AddRestaurantReviewScreen.routeName,
                      name: AddRestaurantReviewScreen.routeName,
                      pageBuilder: (context, state) {
                        final String id = state.params['id'] as String;
                        return NoTransitionPage(
                          child: AddRestaurantReviewScreen(
                              key: state.pageKey, id: id),
                        );
                      },
                    ),
                    GoRoute(
                        name: MenuItemsScreen.routeName,
                        path: MenuItemsScreen.routeName,
                        pageBuilder: (context, state) {
                          return NoTransitionPage(
                            child: MenuItemsScreen(
                                key: state.pageKey, id: state.params['id']!),
                          );
                        },
                        routes: [
                          GoRoute(
                              name: MenuItemDetailsScreen.routeName,
                              path: ':menuItemId',
                              pageBuilder: (context, state) {
                                final String id =
                                    state.params['menuItemId'] as String;
                                final String restaurantId =
                                    state.params['id'] as String;
                                return NoTransitionPage(
                                  child: MenuItemDetailsScreen(
                                    key: state.pageKey,
                                    id: id,
                                    restaurantId: restaurantId,
                                  ),
                                );
                              },
                              routes: [
                                GoRoute(
                                    name: AddMenuItemReviewScreen.routeName,
                                    path: AddMenuItemReviewScreen.routeName,
                                    pageBuilder: (context, state) {
                                      final String id =
                                          state.params['menuItemId'] as String;
                                      final String restaurantId =
                                          state.params['id'] as String;

                                      return NoTransitionPage(
                                          child: AddMenuItemReviewScreen(
                                        key: state.pageKey,
                                        id: id,
                                        restaurantId: restaurantId,
                                      ));
                                    })
                              ]),
                        ]),
                  ],
                ),
              ]),
          GoRoute(
            path: '/profile',
            name: UserProfileScreen.routeName,
            pageBuilder: (context, state) {
              return NoTransitionPage(
                  child: UserProfileScreen(key: state.pageKey));
            },
          )
        ])
  ],
  errorBuilder: (context, state) => RouteErrorScreen(
    errorMsg: state.error.toString(),
    key: state.pageKey,
  ),
);
ceptic
  • 162
  • 1
  • 8

2 Answers2

0

To Navigate to the shell route, the code is given bellow replace the location with your route name

  GoRouter router = GoRouter.of(context);
              router.go(location);

For Handling the authentication i use the redirect method of go router in which emit the state of current appstatus.

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

GoRouter gorouter(AppStatus appStatus) {
  return GoRouter(
    initialLocation: RouteConstants.login,
    navigatorKey: _rootNavigatorKey,
    redirect: (BuildContext context, GoRouterState state) {
      switch (appStatus) {
        case AppStatus.unauthenticated:
          return RouteConstants.login;
        case AppStatus.authenticated:
          if (state.location == RouteConstants.login ||
              state.location == RouteConstants.signUp) {
            return RouteConstants.dashboard;
          } else {
            return state.location;
          }
      }
    },
    routes: [
      ShellRoute(
        navigatorKey: _shellNavigatorKey,
        pageBuilder: (context, state, child) {
          return NoTransitionPage(
              child: App(
            location: state.location,
            child: child,
          ));
        },
        routes: [
          GoRoute(
            path: RouteConstants.dashboard,
            parentNavigatorKey: _shellNavigatorKey,
            pageBuilder: (context, state) {
              return const NoTransitionPage(child: Dashboard());
            },
          ),
          GoRoute(
            path: RouteConstants.history,
            parentNavigatorKey: _shellNavigatorKey,
            pageBuilder: (context, state) {
              return const NoTransitionPage(child: History());
            },
          ),
        ],
      ),
      GoRoute(
        parentNavigatorKey: _rootNavigatorKey,
        path: RouteConstants.login,
        builder: (context, state) {
          return const SignInScreen();
        },
      ),
      GoRoute(
        parentNavigatorKey: _rootNavigatorKey,
        path: RouteConstants.signUp,
        builder: (context, state) {
          return const SignUpScreen();
        },
      ),
    ],
  );
}

Main screen

class AppView extends StatelessWidget {
  const AppView({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      title: 'Lazy Us',
      theme: lightTheme(),
      darkTheme: darkTheme(),
      routerConfig: gorouter(
        context.select((AppBloc bloc) => bloc.state.status),
      ),
    );
  }
}
Mahad
  • 56
  • 5
  • That's exactly what I have written in the code, but what is the location of the ShellRoute? – ceptic Feb 26 '23 at 08:37
  • I updated the response with the full explanation on how to handle authentication with shellroute. – Mahad Feb 26 '23 at 14:07
  • Thank you for updating, But I'm a little confused where to trigger change the enums to authenticated and unauthed. – ceptic Mar 01 '23 at 16:05
  • The ShellRoute is not navigated to directly; you navigate to the children of the ShellRoute. Prior to the children being built, the ancestor ShellRoutes are built, and the cild page is eventually built and nested within the ShellRoutes' builders. – oravecz Mar 04 '23 at 06:04
0

I spent several hours trying to resolve this problem, I don't think it is possible to navigate to a shell route at the moment with GoRoute but however I wa able to find a way that works for me base on the project I am working on. Kindly checkout the code below:

  1. First of all, I added my bottom navigation bar to my route constants.

      static const onboarding = '/';
      static const navigationBar = '/navigationBar';
      static const home = '/home';
      static const unknown = '/unknown';
      static const notifications = '/notifications';
      }```
    
    
  2. Secondly, I created the BottomNavigationScreen.

      final Widget? child;
      const BottomNavigationScreen({super.key, this.child});
    
      @override
      State<BottomNavigationScreen> createState() => _BottomNavigationScreenState();
    }
    
    class _BottomNavigationScreenState extends State<BottomNavigationScreen> {
      int selectedIndex = 0;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: widget.child,
          bottomNavigationBar: BottomNavigationBar(
              currentIndex: calculateSelectedIndex(),
              onTap: (value) => setState(() {
                    selectedIndex = value;
                    switch (value) {
                      case 0:
                        return context.go(AppRoutes.home);
                      case 1:
                        return context.go(AppRoutes.unknown);
                      case 2:
                        return context.go(AppRoutes.notifications);
                      case 3:
                        return context.go(AppRoutes.profile);
                    }
                  }),
              type: BottomNavigationBarType.fixed,
              items: List.generate(
                  items.length,
                  (i) => BottomNavigationBarItem(
                      icon: Icon(
                        items[i],
                        size: 30,
                        color: selectedIndex == i
                            ? const Color(0xff5B53FF)
                            : Colors.black,
                      ),
                      label: ''))),
        );
      }
    
      int calculateSelectedIndex() {
        final location = GoRouter.of(context).location;
        switch (location) {
          case AppRoutes.home:
            return 0;
          case AppRoutes.unknown:
            return 1;
          case AppRoutes.notifications:
            return 2;
          case AppRoutes.profile:
            return 3;
          default:
            return 0;
        }
      }
    }
    
    List<IconData> items = [
      Icons.house,
      Icons.confirmation_num_rounded,
      Icons.notifications,
      Icons.account_circle
    ];```
    
    
    
  3. Thirdly, I defined my shell routes and noticed I also added the BottomNavigationScreen among the routes and gave it and initial screen to display. This is the trick I used.

GoRoute(
       path: AppRoutes.personalInfoScreen3,
       builder: (context, state) => const PersonalInfoScreen3(),
     ),
     GoRoute(
       path: AppRoutes.navigationBar,
       builder: (context, state) =>
           const BottomNavigationScreen(child: HomeScreen()),
     ),
     ShellRoute(
         navigatorKey: _shellNavigatorKey,
         builder: (context, state, child) =>
             BottomNavigationScreen(child: child),
         routes: [
           GoRoute(
             path: AppRoutes.home,
             builder: (context, state) => const HomeScreen(),
           ),
           GoRoute(
             path: AppRoutes.unknown,
             builder: (context, state) => const Scaffold(),
           ),
           GoRoute(
             path: AppRoutes.notifications,
             builder: (context, state) => const NotificationScreen(),
           ),
           GoRoute(
             path: AppRoutes.profile,
             builder: (context, state) => const ProfileScreen(),
           ),
         ])
  1. Lastly, we can navigate to the BottomNavigationScreen after signing up or signing into the app.
CustomButton(onPressed: () => context.push(AppRoutes.navigationBar),
              text: 'Continue',
              color: secondaryColor),