2

I'm developing an Android app using Firebase Authentication (email/password) with Jetpack Compose and Kotlin. The app stores user data locally in DataStore and sets a loggedIn flag to true after successful login and email verification.

On cold start (after killing the app process), FirebaseAuth.getInstance().currentUser returns null, even though DataStore contains valid user data. This causes the app to incorrectly redirect to the onboarding screen instead of restoring the session.

Log:

2025-08-03 10:39:59.094 12114-12114 FirebearStorageCryptoHelper com.example.pawgress E Exception encountered during crypto setup: Keystore cannot load the key with ID: firebear_main_key_id_for_storage_crypto... 2025-08-03 10:39:59.094 12114-12114 FirebearStorageCryptoHelper com.example.pawgress E KeysetManager failed to initialize - unable to decrypt data
2025-08-03 10:39:59.095 12114-12114 PawgressApplication com.example.pawgress D Firebase Auth initialized, currentUser: null
2025-08-03 10:39:59.904 12114-12114 SplashScreen com.example.pawgress D Auth state changed: null Checking local session: userId=bU36t4SAe8fDqrpCxhMfNS2SlKm1, flag=true
2025-08-03 10:40:01.920 12114-12114 UserRepository com.example.pawgress D Has local user data: true

This seems related to Android Keystore failing to decrypt Firebase's authentication tokens, causing the session to be lost. The issue occurs consistently on my Redmi Note 13 running HyperOS 2.0.206.0 (Android 13).

What I've Tried

  • Updated Firebase SDK: Using com.google.firebase:firebase-auth:23.2.0 with firebase-bom:33.16.0 — issue persists.

  • AuthStateListener: Added listener, but it either doesn’t fire or returns null for 10–20 seconds:

firebaseAuth.addAuthStateListener { auth -> 
    Log.d("PawgressApplication", "Auth state changed: ${auth.currentUser}") 
}
  • Polling for currentUser: Tried polling .currentUser with delay, stays null:
while (firebaseUser == null && retries < maxRetries) {
    firebaseUser = firebaseAuth.currentUser
    delay(1000) 
    retries++ 
}
  • Cleared app data: Reset app via settings, logged in again — issue still happens on cold start.

  • Disabled HyperOS optimizations: Turned off system-level optimizations — no effect.

  • Verified email: Email verification works correctly (verified flag true in logs).

  • Checked DataStore: Local user info is accurate (userId, email, loggedIn == true).

  • Checked Google Play Services: Installed, up-to-date, available.

  • Searched for similar issues: Found threads mentioning Keystore issues on MIUI/HyperOS devices, but none offered a working fix.

Code Snippet (SplashScreen Logic)

    var firebaseUser = firebaseAuth.currentUser
    var retries = 0
    val maxRetries = 10
    val retryDelay = 2000L

    Log.d("SplashScreen", "🔍 Initial Firebase user: ${firebaseUser?.uid}")

    var authStateReceived = false
    val authStateListener = FirebaseAuth.AuthStateListener { auth ->
        firebaseUser = auth.currentUser
        authStateReceived = true
        Log.d("SplashScreen", "🔄 Auth state changed: ${firebaseUser?.uid}")
    }

    firebaseAuth.addAuthStateListener(authStateListener)

    while (!authStateReceived && retries < maxRetries) {
        Log.d("SplashScreen", "🔄 Waiting for auth state, retry $retries/$maxRetries")
        delay(retryDelay)
        retries++
    }

    firebaseAuth.removeAuthStateListener(authStateListener)

    val safeUser = firebaseUser
    if (safeUser != null) {
        try {
            safeUser.reload().await()
            Log.d("SplashScreen", "✅ Firebase user reloaded: ${safeUser.uid}, verified: ${safeUser.isEmailVerified}")
        } catch (e: Exception) {
            Log.e("SplashScreen", "Error reloading Firebase user: ${e.message}")
        }
    }

    val localSessionValid = userRepository.hasValidLocalSession()
    val localUser = userRepository.getUser().first()

    val isLoggedIn = safeUser != null &&
            safeUser.isEmailVerified &&
            localUser != null &&
            safeUser.uid == localUser.id &&
            localSessionValid

    val hasLocalUser = userRepository.hasLocalUserData()

    Log.d("SplashScreen", "📊 Final state - firebaseUser: ${safeUser?.uid}, isLoggedIn: $isLoggedIn, hasLocalUser: $hasLocalUser")

    if (isLoggedIn && hasLocalUser) {
        val surveyCompleted = userRepository.isSurveyCompleted().first()
        val petNamed = userRepository.isPetNamedFlow().first()

        Log.d("SplashScreen", "📋 Survey completed: $surveyCompleted, Pet named: $petNamed")

        when {
            !surveyCompleted -> onTimeout("survey/1")
            !petNamed -> onTimeout("pet_naming")
            else -> onTimeout("home")
        }
    } else {
        Log.w("SplashScreen", "⚠️ Incomplete login - redirecting to onboarding")
        onTimeout("onboarding")
    }
} 
1
  • Having the same issue. I am on Android 15 (HyperOS 2.0.202.0). The code was working perfectly and it stopped working all of a sudden. Also, I have verified that the code is working on an other android device. Commented Aug 31 at 14:43

1 Answer 1

0

On cold start (after killing the app process), FirebaseAuth.getInstance().currentUser returns null

That is somewhat expected, as Firebase needs some time to check with the server whether the credentials are still valid. Until that check has completed, currentUser is gonna be null.


That's why you should always use an auth state listener as you do in your first code snippet. The listener will only fire once the check with the server has completed, so you're guaranteed to get the correct, restored auth state. This may indeed takes some time.

If the callback gives null as the current user, either there were no credentials or they couldn't be restored/validated by the server. There is nothing else you can do with the API at this point, the user will have to sign in again.

Polling won't help at all, since the auth state listener already calls your code as soon as it completed the check with the server.


Since there's nothing wrong with your listener code, it apparently takes 10-20 seconds for the check to complete. You'll either have to:

  • hide that delay client-side, i.e. attach the listener as early as possible and show an animation

Or alternatively you can

  • make an assumption about the authentication state. Specifically:
    • If this is the first time the app runs, the user has never signed in before - so you know for a fact that currentUser will remain null. I'm pretty sure the Firebase SDK handles this case already though, so likely you won't have to do anything for this case.
    • If the user was signed in before when they used the app, you can assume that they are still gonna be signed in (since Firebase doesn't expire credentials anyway).
      If you want to do this, store the fact that the user signed in before in some local storage, read it when the app restarts, and use that to determine your flow - rather than waiting for the auth state listener to complete.
      Just keep in mind that this assumption may be false, as the user account may have been deleted or suspected. So you should still use the auth state listener to catch those (hopefully uncommon) cases.

Also see:

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.