Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import com.firebase.ui.auth.configuration.theme.AuthUITheme
import com.firebase.ui.auth.ui.screens.AuthSuccessUiContext
import com.firebase.ui.auth.ui.screens.FirebaseAuthScreen
import com.firebase.ui.auth.util.EmailLinkConstants
import com.firebase.ui.auth.util.displayIdentifier
import com.firebase.ui.auth.util.getDisplayEmail
import com.google.firebase.auth.actionCodeSettings

class HighLevelApiDemoActivity : ComponentActivity() {
Expand Down Expand Up @@ -211,7 +213,7 @@ private fun AppAuthenticatedContent(
when (state) {
is AuthState.Success -> {
val user = uiContext.authUI.getCurrentUser()
val identifier = user?.email ?: user?.phoneNumber ?: user?.uid.orEmpty()
val identifier = user.displayIdentifier()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the state is already smart-cast to AuthState.Success in this block, you can use state.user directly instead of relying on the user variable retrieved from authUI.getCurrentUser(). This is more idiomatic as it ensures you are using the specific user instance that triggered the current state emission.

Suggested change
val identifier = user.displayIdentifier()
val identifier = state.user.displayIdentifier()

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down Expand Up @@ -263,7 +265,7 @@ private fun AppAuthenticatedContent(
}

is AuthState.RequiresEmailVerification -> {
val email = uiContext.authUI.getCurrentUser()?.email ?: stringProvider.emailProvider
val email = uiContext.authUI.getCurrentUser().getDisplayEmail(stringProvider.emailProvider)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the AuthState.RequiresEmailVerification block, you can use state.user directly instead of calling uiContext.authUI.getCurrentUser(). Using the data provided by the state object is safer and more consistent with Compose state management patterns.

Suggested change
val email = uiContext.authUI.getCurrentUser().getDisplayEmail(stringProvider.emailProvider)
val email = state.user.getDisplayEmail(stringProvider.emailProvider)

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down
79 changes: 54 additions & 25 deletions auth/src/main/java/com/firebase/ui/auth/AuthException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

package com.firebase.ui.auth

import android.content.Context
import com.firebase.ui.auth.AuthException.Companion.from
import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
import com.google.firebase.FirebaseException
import com.google.firebase.auth.AuthCredential
import com.google.firebase.auth.FirebaseAuthException
Expand Down Expand Up @@ -341,41 +344,56 @@ abstract class AuthException(
* @return An appropriate [AuthException] subtype
*/
@JvmStatic
fun from(firebaseException: Exception): AuthException {
fun from(firebaseException: Exception, context: Context): AuthException =
from(firebaseException, DefaultAuthUIStringProvider(context))

@JvmStatic
@JvmOverloads
fun from(firebaseException: Exception, stringProvider: AuthUIStringProvider? = null): AuthException {
return when (firebaseException) {
// If already an AuthException, return it directly
is AuthException -> firebaseException

// Handle specific Firebase Auth exceptions first (before general FirebaseException)
is FirebaseAuthInvalidCredentialsException -> {
InvalidCredentialsException(
message = firebaseException.message ?: "Invalid credentials provided",
message = stringProvider?.errorInvalidCredentials.nonEmpty()
?: firebaseException.message
?: "Invalid credentials provided",
cause = firebaseException
)
}

is FirebaseAuthInvalidUserException -> {
when (firebaseException.errorCode) {
"ERROR_USER_NOT_FOUND" -> UserNotFoundException(
message = firebaseException.message ?: "User not found",
message = stringProvider?.errorUserNotFound.nonEmpty()
?: firebaseException.message
?: "User not found",
cause = firebaseException
)

"ERROR_USER_DISABLED" -> InvalidCredentialsException(
message = firebaseException.message ?: "User account has been disabled",
message = stringProvider?.errorUserDisabled.nonEmpty()
?: firebaseException.message
?: "User account has been disabled",
cause = firebaseException
)

else -> UserNotFoundException(
message = firebaseException.message ?: "User account error",
message = stringProvider?.errorUserAccountGeneric.nonEmpty()
?: firebaseException.message
?: "User account error",
cause = firebaseException
)
}
}

is FirebaseAuthWeakPasswordException -> {
WeakPasswordException(
message = firebaseException.message ?: "Password is too weak",
message = stringProvider?.errorWeakPasswordGeneric.nonEmpty()
?: firebaseException.message
?: "Password is too weak",
cause = firebaseException,
reason = firebaseException.reason
)
Expand All @@ -384,92 +402,103 @@ abstract class AuthException(
is FirebaseAuthUserCollisionException -> {
when (firebaseException.errorCode) {
"ERROR_EMAIL_ALREADY_IN_USE" -> EmailAlreadyInUseException(
message = firebaseException.message
message = stringProvider?.errorEmailAlreadyInUse.nonEmpty()
?: firebaseException.message
?: "Email address is already in use",
cause = firebaseException,
email = firebaseException.email
)

"ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL" -> AccountLinkingRequiredException(
message = firebaseException.message
message = stringProvider?.errorAccountExistsDifferentCredential.nonEmpty()
?: firebaseException.message
?: "Account already exists with different credentials",
cause = firebaseException
)

"ERROR_CREDENTIAL_ALREADY_IN_USE" -> AccountLinkingRequiredException(
message = firebaseException.message
message = stringProvider?.errorCredentialAlreadyInUse.nonEmpty()
?: firebaseException.message
?: "Credential is already associated with a different user account",
cause = firebaseException
)

else -> AccountLinkingRequiredException(
message = firebaseException.message ?: "Account collision error",
message = stringProvider?.errorAccountCollisionGeneric.nonEmpty()
?: firebaseException.message
?: "Account collision error",
cause = firebaseException
)
}
}

is FirebaseAuthMultiFactorException -> {
MfaRequiredException(
message = firebaseException.message
message = stringProvider?.errorMfaRequiredFallback.nonEmpty()
?: firebaseException.message
?: "Multi-factor authentication required",
cause = firebaseException
)
}

is FirebaseAuthRecentLoginRequiredException -> {
InvalidCredentialsException(
message = firebaseException.message
message = stringProvider?.errorRecentLoginRequired.nonEmpty()
?: firebaseException.message
?: "Recent login required for this operation",
cause = firebaseException
)
}

is FirebaseAuthException -> {
// Handle FirebaseAuthException and check for specific error codes
when (firebaseException.errorCode) {
"ERROR_TOO_MANY_REQUESTS" -> TooManyRequestsException(
message = firebaseException.message
message = stringProvider?.errorTooManyRequests.nonEmpty()
?: firebaseException.message
?: "Too many requests. Please try again later",
cause = firebaseException
)

else -> UnknownException(
message = firebaseException.message
message = stringProvider?.errorUnknownAuth.nonEmpty()
?: firebaseException.message
?: "An unknown authentication error occurred",
cause = firebaseException
)
}
}

is FirebaseException -> {
// Handle general Firebase exceptions, which include network errors
NetworkException(
message = firebaseException.message ?: "Network error occurred",
message = stringProvider?.errorNetworkGeneric.nonEmpty()
?: firebaseException.message
?: "Network error occurred",
cause = firebaseException
)
}

else -> {
// Check for common cancellation patterns
if (firebaseException.message?.contains(
"cancelled",
ignoreCase = true
) == true ||
if (firebaseException.message?.contains("cancelled", ignoreCase = true) == true ||
firebaseException.message?.contains("canceled", ignoreCase = true) == true
) {
AuthCancelledException(
message = firebaseException.message ?: "Authentication was cancelled",
message = stringProvider?.errorAuthCancelled.nonEmpty()
?: firebaseException.message
?: "Authentication was cancelled",
cause = firebaseException
)
} else {
UnknownException(
message = firebaseException.message ?: "An unknown error occurred",
message = stringProvider?.errorUnknownAuth.nonEmpty()
?: firebaseException.message
?: "An unknown error occurred",
cause = firebaseException
)
}
}
}
}

private fun String?.nonEmpty(): String? = this?.ifEmpty { null }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ class AuthFlowController internal constructor(
*/
fun createIntent(context: Context): Intent {
checkNotDisposed()
return FirebaseAuthActivity.createIntent(context, configuration)
return FirebaseAuthActivity.createIntent(
context = context,
configuration = configuration,
authUI = authUI
)
}

/**
Expand Down
48 changes: 39 additions & 9 deletions auth/src/main/java/com/firebase/ui/auth/FirebaseAuthActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.annotation.RestrictTo
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand Down Expand Up @@ -72,15 +73,16 @@ class FirebaseAuthActivity : ComponentActivity() {

private lateinit var authUI: FirebaseAuthUI
private lateinit var configuration: AuthUIConfiguration
private var launchKey: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

// Extract configuration from cache using UUID key
val configKey = intent.getStringExtra(EXTRA_CONFIGURATION_KEY)
configuration = if (configKey != null) {
configurationCache.remove(configKey)
// Extract configuration and auth instance from cache using UUID key
launchKey = intent.getStringExtra(EXTRA_CONFIGURATION_KEY)
configuration = if (launchKey != null) {
configurationCache[launchKey]
} else {
null
} ?: run {
Expand All @@ -90,7 +92,12 @@ class FirebaseAuthActivity : ComponentActivity() {
return
}

authUI = FirebaseAuthUI.getInstance()
authUI = launchKey?.let { authUICache[it] } ?: run {
// Missing auth instance, finish with error
setResult(RESULT_CANCELED)
finish()
return
}

// Extract email link if present
val emailLink = intent.getStringExtra(EmailLinkConstants.EXTRA_EMAIL_LINK)
Expand Down Expand Up @@ -150,11 +157,17 @@ class FirebaseAuthActivity : ComponentActivity() {
}

override fun onDestroy() {
super.onDestroy()
// Reset auth state when activity is destroyed
if (!isFinishing) {
if (isFinishing) {
launchKey?.let { key ->
configurationCache.remove(key)
authUICache.remove(key)
}
} else {
// Preserve cached launch state so the recreated activity can recover it.
authUI.updateAuthState(AuthState.Idle)
}

super.onDestroy()
}

companion object {
Expand Down Expand Up @@ -191,14 +204,31 @@ class FirebaseAuthActivity : ComponentActivity() {
*/
internal fun createIntent(
context: Context,
configuration: AuthUIConfiguration
configuration: AuthUIConfiguration,
authUI: FirebaseAuthUI = FirebaseAuthUI.getInstance()
): Intent {
val configKey = UUID.randomUUID().toString()
configurationCache[configKey] = configuration
authUICache[configKey] = authUI

return Intent(context, FirebaseAuthActivity::class.java).apply {
putExtra(EXTRA_CONFIGURATION_KEY, configKey)
}
}

/**
* Clears cached launch state. This method is intended for testing purposes only.
*
* @suppress This is an internal API and should not be used in production code.
* @RestrictTo RestrictTo.Scope.TESTS
*/
@JvmStatic
@RestrictTo(RestrictTo.Scope.TESTS)
fun clearLaunchStateCache() {
configurationCache.clear()
authUICache.clear()
}

private val authUICache = ConcurrentHashMap<String, FirebaseAuthUI>()
}
}
Loading
Loading