3

I'm using the go_router package in Flutter for app routing, but I'm running into issues when I use it alongside the default Flutter Appbar and Drawer widgets.

  • Typical "go" and "push" methods that I'm calling from clicks in the Drawer don't work as expected when pushing the back button.
  • The AppBar doesn't imply the leading back or menu behavior.

Is there something particular that needs to be done to get go_router to play nicely with the Flutter Navigator? Maybe I need to set some particular fields or a global key?

Here's what my setup looks like:

class MainApp extends ConsumerStatefulWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  ConsumerState<MainApp> createState() => _MainAppState();
}

class _MainAppState extends ConsumerState<MainApp> {
  late GoRouter router;
  late Future<void> jwtInit;

  @override
  void initState() {
    jwtInit = ref.read(jwtProvider.notifier).init();

    router = GoRouter(
      routes: [
        GoRoute(
          path: "/",
          name: "home",
          pageBuilder: (context, state) => MaterialPage<void>(
            key: state.pageKey,
            child: const HomeScreen(),
          ),
        ),
        GoRoute(
          path: "/settings",
          name: "settings",
          pageBuilder: (context, state) => MaterialPage<void>(
            key: state.pageKey,
            child: const SettingsScreen(),
          ),
        ),
        GoRoute(
          path: "/programs",
          name: "programs",
          pageBuilder: (context, state) => MaterialPage<void>(
            key: state.pageKey,
            child: const ProgramScreen(),
          ),
        ),
        GoRoute(
          path: "/programs/:programId",
          name: "program",
          pageBuilder: (context, state) => MaterialPage<void>(
            key: state.pageKey,
            child: ProgramDetailsScreen(
              // programId: 39,
              programId: int.parse(state.params["programId"]!),
            ),
          ),
        ),
        GoRoute(
            path: "/activity/:activityId",
            name: "activity",
            pageBuilder: (context, state) {
              return MaterialPage<void>(
                key: state.pageKey,
                child: ActivityScreen(
                  id: int.parse(state.params["activityId"]!),
                ),
              );
            }),
        GoRoute(
          path: "/login",
          name: "login",
          pageBuilder: (context, state) => MaterialPage<void>(
            key: state.pageKey,
            child: const LoginScreen(),
          ),
        ),
      ],
      errorPageBuilder: (context, state) => MaterialPage<void>(
        key: state.pageKey,
        child: const Scaffold(
          body: Center(
            child: Text("PAGE NOT FOUND!"),
          ),
        ),
      ),
      // refreshListenable: api,
      redirect: (context, state) {
        final loggedIn = ref.read(jwtProvider.notifier).isLoggedIn;
        final goingToLogin = state.location == '/login';

        // the user is not logged in and not headed to /login, they need to login
        if (!loggedIn && !goingToLogin) return '/login';

        // the user is logged in and headed to /login, no need to login again
        if (loggedIn && goingToLogin) return '/';

        // no need to redirect - go to intended page
        return null;
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    //The reason for this FutureBuilder is to wait for the api key to
    //load from storage before allowing the initial page to route. Otherwise
    //the routing goes too fast and it looks logged out.
    return FutureBuilder(
        future: jwtInit,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            //Run the UI
            return MaterialApp.router(
              debugShowCheckedModeBanner: false,
              title: 'MyApp',
              theme: MyTheme.darkTheme(context),
              routeInformationProvider: router.routeInformationProvider,
              routeInformationParser: router.routeInformationParser,
              routerDelegate: router.routerDelegate,
            );
          } else {
            return Container();
          }
        });
  }
}

In my drawer, I'm calling the navigation like this:

onTap: () {
  context.push("/settings");
}
Doughy
  • 4,115
  • 6
  • 36
  • 39

1 Answers1

-1

Use ShellRouter with GoRouter to meet your requirement!


Example:

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: ScaffoldAppAndBottomBar(child: child));
      },
      routes: [
        GoRoute(
          parentNavigatorKey: _shellNavigatorKey,
          path: '/home',
          pageBuilder: (context, state) {
            return NoTransitionPage(
              child: Scaffold(
                body: const Center(
                  child: Text("Home"),
                ),
              ),
            );
          },
        ),
        GoRoute(
          path: '/',
          parentNavigatorKey: _shellNavigatorKey,
          pageBuilder: (context, state) {
            return const NoTransitionPage(
              child: Scaffold(
                body: Center(child: Text("Initial")),
              ),
            );
          },
        ),
      ],
    ),
  ],
);

ScaffoldAppAndBottomBar

class ScaffoldAppAndBottomBar extends StatelessWidget {
  Widget child;
  ScaffoldAppAndBottomBar({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          centerTitle: true,
          title: const Text(
            "App Bar",
          ),
          backgroundColor: Colors.amber,
        ),
        body: SafeArea(child: child),
        bottomNavigationBar: Container(
          color: Colors.blue,
          height: 56,
          width: double.infinity,
          child: const Center(child: Text("Bottom Navigation Bar")),
        ),
        floatingActionButton: FloatingActionButton(
          backgroundColor: Colors.red,
          onPressed: () {
            context.go('/home');
          },
          child: const Icon(Icons.home),
        ));
  }
}

Output:

Initially

enter image description here

After pressing floating button

enter image description here


Refer detailed code and explaination of bottom NavigationBar using ShellRoute and GoRouter here

krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
  • Thanks for sharing your answer. It sounds like the ScaffoldAppAndBottomBar is still causing some issues with state retention. Have you tried using a different approach or looking into ways to prevent the rebuild? @Krisna Acharya – Benjamin Sx Jan 25 '23 at 17:01
  • @BenjaminSx Use `StatefulShellRoute`. – AllinProgram Aug 23 '23 at 02:23