DEV Community

Aris Imad Eddine
Aris Imad Eddine

Posted on

Firebase Auth loses authentication state on Android app restart - user gets logged out when app is killed and reopened

I'm developing a Flutter app using Firebase Auth, and I'm experiencing a persistent issue where users get logged out every time they close the app completely (kill from recent apps) and reopen it. The authentication should persist automatically on mobile platforms, but it's not working.

Problem:

  • User logs in successfully ✅
  • User closes app normally (minimize) - stays logged in ✅
  • User kills app completely and reopens - gets logged out and redirected to login❌

main.dart:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  try {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    await SecurityService.initialize();
    AntiDebugService.startMonitoring();
    ObfuscationUtils.executeDummyOperations();
  } catch (error) {
    debugPrint('Initialization error: $error');
  }

  runApp(const KhedamApp());

  _initializeBackgroundServices();
}

void _initializeBackgroundServices() async {
  try {
    FlutterError.onError = (FlutterErrorDetails details) {
      RuntimeReportService.reportCrash(
          details.exception, details.stack ?? StackTrace.current);
    };

    runZonedGuarded(() {}, (error, stackTrace) {
      RuntimeReportService.reportCrash(error, stackTrace);
    });

    await RuntimeReportService.init();
    AppLifecycleManager().initialize();

    final notificationService = NotificationService();
    await notificationService.initPushNotifications();

    SanctionCleanupService.startPeriodicCleanup();
  } catch (error) {
    debugPrint('Background initialization error: $error');
  }
}

class KhedamApp extends StatefulWidget {
  const KhedamApp({super.key});

  @override
  State<KhedamApp> createState() => _KhedamAppState();
}

class _KhedamAppState extends State<KhedamApp> with WidgetsBindingObserver {
  final LocaleProvider _localeProvider = LocaleProvider();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      DeviceSessionService.updateActivity();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          Provider<AuthService>(create: (_) => AuthService()),
          ChangeNotifierProvider<LocaleProvider>.value(value: _localeProvider),
        ],
        child: Consumer<LocaleProvider>(
          builder: (context, localeProvider, _) => MaterialApp(
            title: 'Khedam',
            routes: {'/login': (context) => const LoginScreen()},
            debugShowCheckedModeBanner: false,
            localizationsDelegates: const [
              AppLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: const [
              Locale('fr'),
              Locale('ar'),
              Locale('en'),
            ],
            locale: localeProvider.locale,
            theme: ThemeData(
              colorScheme: ColorScheme.fromSeed(
                seedColor: AppColors.primary,
                brightness: Brightness.light,
              ),
              textTheme: GoogleFonts.notoSansTextTheme(),
              useMaterial3: true,
              appBarTheme: const AppBarTheme(
                backgroundColor: AppColors.primary,
                foregroundColor: Colors.white,
                elevation: 0,
              ),
              elevatedButtonTheme: ElevatedButtonThemeData(
                style: ElevatedButton.styleFrom(
                  backgroundColor: AppColors.primary,
                  foregroundColor: Colors.white,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
              cardTheme: CardTheme(
                elevation: 2,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
            home: const SplashScreen(),
          ),
        ));
  }
}
Enter fullscreen mode Exit fullscreen mode

splash_screen.dart:

class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  bool _hasNavigated = false;

  @override
  void initState() {
    super.initState();
    _initializeApp();
    Future.delayed(const Duration(seconds: 10), () {
      if (!_hasNavigated && mounted) {
        if (FirebaseAuth.instance.currentUser != null) {
          RuntimeReportService.reportSplashTimeout();
        }
      }
    });
  }

  void _initializeApp() async {
    final splashStart = DateTime.now();
    const minSplashDuration = Duration(milliseconds: 2000);

    try {
      _initializeSplashServices();

      final user = FirebaseAuth.instance.currentUser;

      final elapsed = DateTime.now().difference(splashStart);
      if (elapsed < minSplashDuration) {
        await Future.delayed(minSplashDuration - elapsed);
      }

      if (mounted && !_hasNavigated) {
        _hasNavigated = true;
        _navigateBasedOnAuthState(user);
      }
    } catch (e) {
      final elapsed = DateTime.now().difference(splashStart);
      if (elapsed < minSplashDuration) {
        await Future.delayed(minSplashDuration - elapsed);
      }

      if (mounted && !_hasNavigated) {
        _hasNavigated = true;
        _navigateBasedOnAuthState(null);
      }
    }
  }

  void _initializeSplashServices() async {
    try {
      SecurityService.performRuntimeCheck();
      AntiDebugService.checkPoint();

      _startSessionHeartbeat();
    } catch (e) {
      debugPrint('Splash initialization error: $e');
    }
  }

  void _startSessionHeartbeat() {
    Future.delayed(const Duration(minutes: 5), () {
      DeviceSessionService.updateActivity();
      if (mounted) _startSessionHeartbeat();
    });
  }

  void _navigateBasedOnAuthState(User? user) async {
    if (user != null) {
      debugPrint('✅ User IS authenticated redirect to home');
      DeviceSessionService.updateActivity();

      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const HomeScreen()),
        );
      }
    } else {
      debugPrint('NO authenticated user redirect to Login');

      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const LoginScreen()),
        );
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

auth_service.dart:

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  User? get currentUser => _auth.currentUser;
  Stream<User?> get authStateChanges => _auth.authStateChanges();
}
Future<UserModel?> signInWithEmailAndPassword(String email, String password) async {
  UserCredential result = await _auth.signInWithEmailAndPassword(
    email: email,
    password: password,
  );

  if (result.user != null) {
    return userModel; // Return user data
  }
  return null;
}
Future<void> signOut() async {
  await DeviceSessionService.endSession();
  await _googleSignIn.signOut();
  await _auth.signOut();
}
Enter fullscreen mode Exit fullscreen mode

i've tried using StreamBuilder with authStateChanges(), but failed
i've tried using authStateChanges().first in splash screen, but failed
Both methods fail - the auth state returns null on cold app startup even though the user was previously authenticated, the logs say:

Connection state: ConnectionState.active
Has data: false
User: NULL (NULL)
Enter fullscreen mode Exit fullscreen mode

it shows that firebase auth is actively returning null even though the user was authenticated before killing the app.

Top comments (0)