Asked 1 month ago by StarPilot007
Why Does Navigator.pushAndRemoveUntil Fail in initState with FirebaseAuth authStateChanges?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by StarPilot007
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm using Firebase Authentication in my Flutter app to listen for auth state changes and navigate when a user logs in. Although the auth state change is detected (confirmed by the print logs), the navigation in initState doesn't work.
./main.dart:
DARTimport 'package:app/authentication/email_auth.dart'; import 'package:app/authentication/landing.dart'; import 'package:app/authentication/signup.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'authentication/login.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { User? _user; void initState() { super.initState(); FirebaseAuth.instance.authStateChanges().listen((user) { print("User state changed: \${user?.uid}"); FocusManager.instance.primaryFocus?.unfocus(); setState(() { _user = user; }); if (user != null) { WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => HomeScreen()), (route) => false, ); }); } }); } Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarIconBrightness: ThemeMode.system == ThemeMode.light ? Brightness.dark : Brightness.light)); return MaterialApp( // app data title: 'MyApp', // app navigation routes: { Login.routeName: (context) => const Login(), Signup.routeName: (context) => const Signup(), EmailAuth.routeName: (context) => const EmailAuth(), Landing.routeName: (context) => const Landing(), HomeScreen.routeName: (context) => HomeScreen(), }, home: Landing(), themeMode: ThemeMode.dark, theme: ThemeData(scaffoldBackgroundColor: Color(0xff222531)), darkTheme: ThemeData( scaffoldBackgroundColor: Color(0xff222531), bottomAppBarTheme: BottomAppBarTheme(color: Colors.red), useMaterial3: true, textTheme: TextTheme( labelMedium: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20), labelSmall: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13), bodyLarge: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w500, fontSize: 24), headlineSmall: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w400, fontSize: 22), displayMedium: GoogleFonts.montserrat( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 40), headlineLarge: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w400)), ), ); } } class HomeScreen extends StatelessWidget { HomeScreen({ super.key, }); static const routeName = "/home"; final user = FirebaseAuth.instance.currentUser; Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onTap: () async { print("pressed"); await FirebaseAuth.instance.signOut(); }, child: Center( child: Text( user?.email ?? "hello", style: Theme.of(context).textTheme.headlineLarge, ), ), ), ); } }
Issue:
I've tried multiple solutions including:
Expected behavior: Upon successful sign-in, the user should be navigated to the HomeScreen. Instead, only the sign-in succeeds without any navigation.
What can I do to fix this issue and ensure the navigation occurs when the user logs in?
Comments 0
•Answers 2
•Views 39
Answer by TychoAI Agent • 1 month ago
Below is one common solution: instead of pushing a new route inside initState (which can lead to problems because the MaterialApp already “owns” its navigation), restructure your app to choose which screen to show based on the auth status. For example, wrap your home widget in a widget that listens to auth changes and then returns the correct child. This “AuthWrapper” pattern avoids trying to navigate imperatively during widget initialization. Here’s how you can refactor your code:
DARTimport 'package:app/authentication/email_auth.dart'; import 'package:app/authentication/landing.dart'; import 'package:app/authentication/signup.dart'; import 'package:app/authentication/login.dart'; import 'package:app/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarIconBrightness: ThemeMode.system == ThemeMode.light ? Brightness.dark : Brightness.light)); return MaterialApp( title: 'MyApp', routes: { Login.routeName: (context) => const Login(), Signup.routeName: (context) => const Signup(), EmailAuth.routeName: (context) => const EmailAuth(), Landing.routeName: (context) => const Landing(), HomeScreen.routeName: (context) => HomeScreen(), }, // Instead of a fixed home widget, use an auth wrapper. home: const AuthWrapper(), themeMode: ThemeMode.dark, theme: ThemeData(scaffoldBackgroundColor: const Color(0xff222531)), darkTheme: ThemeData( scaffoldBackgroundColor: const Color(0xff222531), bottomAppBarTheme: const BottomAppBarTheme(color: Colors.red), useMaterial3: true, textTheme: TextTheme( labelMedium: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20), labelSmall: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13), bodyLarge: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w500, fontSize: 24), headlineSmall: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w400, fontSize: 22), displayMedium: GoogleFonts.montserrat( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 40), headlineLarge: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w400), ), ), ); } } class AuthWrapper extends StatelessWidget { const AuthWrapper({super.key}); Widget build(BuildContext context) { // Listen to auth state changes. The builder will be called whenever the auth state updates. return StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { // If the snapshot has data, it means the user is signed in. if (snapshot.hasData && snapshot.data != null) { return HomeScreen(); } else { return const Landing(); } }, ); } }
AuthWrapper Widget
Instead of trying to navigate imperatively in initState, the AuthWrapper widget uses a StreamBuilder to watch the auth state. When a user signs in (or out), the builder rebuilds and instantly shows the appropriate screen (e.g., HomeScreen for signed in users or Landing for non-authenticated users).
Benefits
If you must perform a navigation call (for example, if your app flow requires side effects), consider using a GlobalKey for your Navigator. For instance:
DARTfinal GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Then, pass it to your MaterialApp via the navigatorKey property and call:
DARTnavigatorKey.currentState?.pushAndRemoveUntil( MaterialPageRoute(builder: (context) => HomeScreen()), (route) => false, );
Always ensure that your navigation calls are made when the context is guaranteed to be mounted. The wrapper pattern is often a cleaner approach because it removes the timing issues.
Switching to one of these methods should resolve your issue with navigation not occurring, ensuring the UI reflects the authentication state.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by QuasarNomad325 • 1 month ago
Ok so here's how i fixed it:
I moved to contextless navigation, so I defined a GlobalKey<NavigatorState>
globally in main.dart:
./main.dart:
DART// global navigation final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Then inside initState()
(main.dart
):
DARTFirebaseAuth.instance.authStateChanges().listen((user) { FocusManager.instance.primaryFocus?.unfocus(); setState(() { _user = user; }); if (user != null) { navigatorKey.currentState?.pushNamed("/home"); } });
And in MaterialApp()
(main.dart
) you have to add this line:
DARTnavigatorKey: navigatorKey,
However, I am yet to investigate the potential drawbacks with this approach (when I find them I will list them down below) 👇
You must replace every Navigator
call with the following:
navigatorKey.currentState?.<method eg pushNamed()>
Thanks for the help guys!
No comments yet.
No comments yet.