flutternavigationflutter-navigationflutter-go-routergorouter

GoRouter always shows only one page


Problem: My app always displays the login screen regardless of whatever kind of navigation I make (like setting the initial location to another page, context.push, context.go, etc). I have implemented a redirecting function in the authentication system that redirects user back to the login page if they are unauthenticated but that does not seem to be the culprit (the two print statements: "authState changes" and "Redirect!" did not get called when I pressed the navigating button).

My main.dart file:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sneaker_app/models/cart.dart';
import 'package:sneaker_app/routes/app_route_config.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:sneaker_app/services/auth_service.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // Referesh GoRouter configuration every time authState changes (Authentication guaranteed)
  FirebaseAuth.instance.authStateChanges().listen((User? user) {

    print("authState changes");

    MyAppRouter.router.refresh();
  });

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider<Cart>(create: (context) => Cart()),
          ChangeNotifierProvider<AuthService>(
              create: (context) => AuthService())
        ],
        child: MaterialApp.router(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
              colorScheme: ColorScheme.fromSeed(seedColor: Colors.grey)),
          routerConfig: MyAppRouter.router,
        ));
  }
}

My App router configs:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:sneaker_app/models/shopee_tile.dart';
import 'package:sneaker_app/pages/home_page.dart';
import 'package:sneaker_app/pages/item_page.dart';
import 'package:sneaker_app/pages/login_page.dart';
import 'package:sneaker_app/pages/signup_page.dart';
import 'package:sneaker_app/routes/app_route_constants.dart';
import 'package:sneaker_app/services/auth_service.dart';

class MyAppRouter {
  static final GoRouter router = GoRouter(
    initialLocation: "/login",
    routes: [
      GoRoute(
          name: MyAppRouteConstants.homeRouteName,
          path: "/",
          builder: (context, state) => HomePage()),
      GoRoute(
          name: MyAppRouteConstants.itemRouteName,
          path: "/item/:id",
          builder: (context, state) {
            ShopeeTile item = state.extra as ShopeeTile;
            return ItemPage(
              id: int.parse(state.pathParameters["id"]!),
              item: item,
            );
          }),
      GoRoute(
          name: MyAppRouteConstants.loginRouteName,
          path: "/login",
          builder: (context, state) => LoginPage()),
      GoRoute(
          name: MyAppRouteConstants.signupRouteName,
          path: "/signup",
          builder: (context, state) => SignupPage()),
    ],
    redirect: (BuildContext context, GoRouterState state) async {
      final bool loggedIn =
          Provider.of<AuthService>(context, listen: false).user != null;
      final bool loggingIn = state.matchedLocation == "/login";

      print("Redirect!");

      if (!loggedIn) return "/login";
      if (loggingIn) return "/";
      // no need to redirect at all
      return null;
    },
  );
}

I can ensure that the constants in MyAppRouteConstants match their path

Here is the button widget on the login page

TextButton(
              child: Text('Sign up'),
              onPressed: () {
                context.pushNamed(MyAppRouteConstants.signupRouteName);
              },
            ),

I tried to debug the problem by putting print statements but I couldn't find out why the app only stuck to the login page and did not navigate at all.


Solution

  • With the code snippets you provided, my first guess is that the problem lies in the following line:

    routerConfig: MyAppRouter.router,
    

    Since you apparently have built-in authentication to decide where the user lands, the build method of MyApp is being called again. However, due to this line, a new GoRouter object is being created because you are assigning a new object to router in MyAppRouter. And this config then takes the LoginPage (/login) as the initialLocation.

    As a test, try storing your GoRouter object in your main.dart file as a global variable:

    GoRouter myRouter = GoRouter(...)
    

    Now pass this variable as routerConfig. This might already be the solution.