Search code examples
flutterdartfirebase-authenticationnavigation

Use StreamBuilder as root of navigation stack with GoRouter


Currently I have a StreamBuilder switching between a HomePage and LandingPage depending on the current auth state. The issue I have encountered is that I cannot pop the stack to the original /landing directory on a state change. This means that when a user logs in, the AuthPage remains on the top of the stack and the StreamBuilder builds the HomePage underneath.

AuthGate

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

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        return snapshot.hasData ? const HomePage() : const LandingPage();
      },
    );
  }
}

LandingPage This pushes the AuthPage to the stack.

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

  ...

        Row(
          children: <Widget>[
            FloatingActionButton.extended(
              heroTag: UniqueKey(),
              onPressed: () {
                context.push('/auth');
              },
              label: const Text('Get started'),
            ),
            FloatingActionButton.extended(
              heroTag: UniqueKey(),
              onPressed: () {
                context.push('/auth');
              },
              label: const Text('Log in'),
            ),
          ],
        )

  ...

  }
}

Stack before auth change

Stack after auth change

Note how the AuthPage remains on the top of the stack but the Widget under StreamBuilder changes to the HomePage

(This is my first Stack question so please feel free to ask me to amend any information etc.)


Solution

  • If you are using GoRouter, then what you want to achieve should be done through GoRouter similarly to this:

    GoRouter(
            refreshListenable:
                GoRouterRefreshListenable(FirebaseAuth.instance.authStateChanges()),
            initialLocation: '/auth',
            routes: <GoRoute>[
              GoRoute(
                path: '/landing',
                name: 'landing',
                builder: (context, state) {
                  return LandingPage()
                },
                routes: [
                  GoRoute(
                    path: 'auth',
                    name: 'auth',
                    builder: (context, state) => const AuthPage(),
                  ),
                ],
              ),
              GoRoute(
                path: '/home',
                name: 'home',
                builder: (context, state) => const HomePage(),
              ),
            ],
            errorBuilder: (context, state) {
              return const Scaffold(
                body: Text('404'),
              );
            },
            redirect: (context, state) async {
              final userRepo = injector.get<UserRepository>();
    
              final user = FirebaseAuth.instance;
    
              const authPaths = [
                '/landing',
                '/landing/auth',
              ];
    
              bool isAuthPage = authPaths.contains(state.subloc);
    
              if(user != null) {
                if (isAuthPage) {
                  return '/home';
                }
              }
              if(!isAuthPage) {
                return '/auth';
              }
              return null;
    
              
            },
          );
    
    class GoRouterRefreshListenable extends ChangeNotifier {
      GoRouterRefreshListenable(Stream stream) {
        notifyListeners();
        _subscription = stream.asBroadcastStream().listen(
          (_) {
            notifyListeners();
          },
        );
      }
    
      late final StreamSubscription _subscription;
    
      @override
      void dispose() {
        _subscription.cancel();
        super.dispose();
      }
    }
    
    

    Please also read documentation on of GoRouter.