I'm using Firebase Authentication in my Flutter app to detect user authentication state changes and navigate accordingly. However, the navigation inside initState
does not work.
./main.dart:
import '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});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
User? _user;
@override
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,
);
});
}
});
}
@override
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;
@override
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:
print()
logs).Navigator.pushAndRemoveUntil
does not work, and the screen does not change.How can I fix this issue and ensure the navigation happens when the user logs in?
Things I Have Tried:
Navigator.pushAndRemoveUntil
inside WidgetsBinding.instance.addPostFrameCallback
Navigator
Future.delayed(Duration(milliseconds: 100), () {...})
build()
instead of initState
MaterialPageRoute
is correctly definedI was expecting the sign-in to go ahead and the user be navigated to the homePage() page. But in reality the sign in is successful while the navigation is not!
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:
// global navigation
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
then inside initState()
(main.dart
):
FirebaseAuth.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:
navigatorKey: 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!