From 9865ecc7b0b92a1f212bda370e6e4b804ad13386 Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 30 Jul 2026 17:43:14 +0100 Subject: [PATCH 1/3] fix(auth): report Google/Facebook/OAuth/Anonymous sign-in failures via onSignInFailure --- .../AnonymousAuthProvider+FirebaseAuthUI.kt | 9 ++- .../FacebookAuthProvider+FirebaseAuthUI.kt | 5 ++ .../GoogleAuthProvider+FirebaseAuthUI.kt | 4 ++ .../OAuthProvider+FirebaseAuthUI.kt | 4 ++ .../ui/auth/ui/screens/FirebaseAuthScreen.kt | 22 ++++--- ...AnonymousAuthProviderFirebaseAuthUITest.kt | 34 +++++++++++ .../GoogleAuthProviderFirebaseAuthUITest.kt | 60 +++++++++++++++++++ 7 files changed, 128 insertions(+), 10 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt index 65ea606dd..060788a8a 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt @@ -14,13 +14,18 @@ import kotlinx.coroutines.tasks.await /** * Creates a remembered launcher function for anonymous sign-in. * + * @param config Authentication UI configuration + * @param onSignInFailure Callback invoked with the resulting [AuthException] on failure * @return A launcher function that starts the anonymous sign-in flow when invoked * * @see signInAnonymously * @see createOrLinkUserWithEmailAndPassword for upgrading anonymous accounts */ @Composable -internal fun FirebaseAuthUI.rememberAnonymousSignInHandler(config: AuthUIConfiguration): () -> Unit { +internal fun FirebaseAuthUI.rememberAnonymousSignInHandler( + config: AuthUIConfiguration, + onSignInFailure: (AuthException) -> Unit = {}, +): () -> Unit { val context = androidx.compose.ui.platform.LocalContext.current val coroutineScope = rememberCoroutineScope() return remember(this) { @@ -31,9 +36,11 @@ internal fun FirebaseAuthUI.rememberAnonymousSignInHandler(config: AuthUIConfigu } catch (e: AuthException) { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) + onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index d6a9622e7..445799018 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.launch * @param config The [AuthUIConfiguration] containing authentication settings * @param provider The [AuthProvider.Facebook] configuration with scopes and credential provider * @param loginManagerProvider Provides logout operations to clear stale Facebook sessions + * @param onSignInFailure Callback invoked with the resulting [AuthException] on failure * * @return A launcher function that starts the Facebook sign-in flow when invoked * @@ -58,6 +59,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( config: AuthUIConfiguration, provider: AuthProvider.Facebook, loginManagerProvider: AuthProvider.Facebook.LoginManagerProvider = AuthProvider.Facebook.DefaultLoginManagerProvider(), + onSignInFailure: (AuthException) -> Unit = {}, ): () -> Unit { val coroutineScope = rememberCoroutineScope() val callbackManager = remember { CallbackManager.Factory.create() } @@ -87,9 +89,11 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( } catch (e: AuthException) { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) + onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } @@ -106,6 +110,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( authException ) ) + onSignInFailure(authException) } }) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt index 89837df3e..61be0fa9a 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.launch * @param context Android context for Credential Manager * @param config Authentication UI configuration * @param provider Google provider configuration with server client ID and optional scopes + * @param onSignInFailure Callback invoked with the resulting [AuthException] on failure * @return A callback function that initiates Google Sign-In when invoked * * @see signInWithGoogle @@ -57,6 +58,7 @@ internal fun FirebaseAuthUI.rememberGoogleSignInHandler( context: Context, config: AuthUIConfiguration, provider: AuthProvider.Google, + onSignInFailure: (AuthException) -> Unit = {}, ): () -> Unit { val coroutineScope = rememberCoroutineScope() return remember(this, config) { @@ -66,9 +68,11 @@ internal fun FirebaseAuthUI.rememberGoogleSignInHandler( signInWithGoogle(context, config, provider) } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) + onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt index add6bb235..7d6b02952 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.tasks.await * * @param config Authentication UI configuration * @param provider OAuth provider configuration + * @param onSignInFailure Callback invoked with the resulting [AuthException] on failure * * @return Lambda that triggers OAuth sign-in when invoked * @@ -54,6 +55,7 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler( activity: Activity?, config: AuthUIConfiguration, provider: AuthProvider.OAuth, + onSignInFailure: (AuthException) -> Unit = {}, ): () -> Unit { val coroutineScope = rememberCoroutineScope() activity ?: throw IllegalStateException( @@ -73,9 +75,11 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler( ) } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) + onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt index 848f0693a..993767290 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt @@ -188,6 +188,7 @@ fun FirebaseAuthScreen( ) ) }, + onSignInFailure = onSignInFailure, ) val continueWithProvider: (String) -> Unit = { providerId -> configuration.providers.find { it.providerId == providerId }?.let { onProviderSelected(it) } @@ -1012,6 +1013,7 @@ private fun FirebaseAuthUI.rememberOnProviderSelected( config: AuthUIConfiguration, onNavigate: (AuthRoute) -> Unit, onUnknownProvider: ((AuthProvider) -> Unit)? = null, + onSignInFailure: (AuthException) -> Unit = {}, ): (AuthProvider) -> Unit { val anonymousProvider = config.providers.filterIsInstance().firstOrNull() val googleProvider = config.providers.filterIsInstance().firstOrNull() @@ -1023,16 +1025,18 @@ private fun FirebaseAuthUI.rememberOnProviderSelected( val twitterProvider = config.providers.filterIsInstance().firstOrNull() val genericOAuthProviders = config.providers.filterIsInstance() - val onSignInAnonymously = anonymousProvider?.let { rememberAnonymousSignInHandler(config) } - val onSignInWithGoogle = googleProvider?.let { rememberGoogleSignInHandler(context, config, it) } - val onSignInWithFacebook = facebookProvider?.let { rememberSignInWithFacebookLauncher(context, config, it) } - val onSignInWithApple = appleProvider?.let { rememberOAuthSignInHandler(context, activity, config, it) } - val onSignInWithGithub = githubProvider?.let { rememberOAuthSignInHandler(context, activity, config, it) } - val onSignInWithMicrosoft = microsoftProvider?.let { rememberOAuthSignInHandler(context, activity, config, it) } - val onSignInWithYahoo = yahooProvider?.let { rememberOAuthSignInHandler(context, activity, config, it) } - val onSignInWithTwitter = twitterProvider?.let { rememberOAuthSignInHandler(context, activity, config, it) } + val onSignInAnonymously = anonymousProvider?.let { rememberAnonymousSignInHandler(config, onSignInFailure) } + val onSignInWithGoogle = googleProvider?.let { rememberGoogleSignInHandler(context, config, it, onSignInFailure) } + val onSignInWithFacebook = facebookProvider?.let { + rememberSignInWithFacebookLauncher(context, config, it, onSignInFailure = onSignInFailure) + } + val onSignInWithApple = appleProvider?.let { rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } + val onSignInWithGithub = githubProvider?.let { rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } + val onSignInWithMicrosoft = microsoftProvider?.let { rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } + val onSignInWithYahoo = yahooProvider?.let { rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } + val onSignInWithTwitter = twitterProvider?.let { rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } val genericOAuthHandlers = genericOAuthProviders.associateWith { - rememberOAuthSignInHandler(context, activity, config, it) + rememberOAuthSignInHandler(context, activity, config, it, onSignInFailure) } return { provider -> diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProviderFirebaseAuthUITest.kt index 6e71c4170..18f396f9b 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProviderFirebaseAuthUITest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProviderFirebaseAuthUITest.kt @@ -15,6 +15,7 @@ package com.firebase.ui.auth.configuration.auth_provider import android.content.Context +import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.AuthException import com.firebase.ui.auth.AuthState @@ -38,6 +39,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers @@ -54,6 +56,9 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class AnonymousAuthProviderFirebaseAuthUITest { + @get:Rule + val composeTestRule = createComposeRule() + @Mock private lateinit var mockFirebaseAuth: FirebaseAuth @@ -214,6 +219,35 @@ class AnonymousAuthProviderFirebaseAuthUITest { assertThat(errorState.exception).isInstanceOf(AuthException.UnknownException::class.java) } + // ============================================================================================= + // rememberAnonymousSignInHandler - onSignInFailure reporting + // ============================================================================================= + + @Test + fun `rememberAnonymousSignInHandler reports failure via onSignInFailure immediately, at the source`() { + val networkException = FirebaseNetworkException("Network error") + val taskCompletionSource = TaskCompletionSource() + taskCompletionSource.setException(networkException) + `when`(mockFirebaseAuth.signInAnonymously()).thenReturn(taskCompletionSource.task) + + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val reportedFailures = mutableListOf() + var launcher: (() -> Unit)? = null + + composeTestRule.setContent { + launcher = instance.rememberAnonymousSignInHandler( + config = config, + onSignInFailure = { reportedFailures.add(it) }, + ) + } + + composeTestRule.runOnIdle { launcher?.invoke() } + composeTestRule.waitForIdle() + + assertThat(reportedFailures).hasSize(1) + assertThat(reportedFailures.single()).isInstanceOf(AuthException.NetworkException::class.java) + } + // ============================================================================================= // Anonymous Account Upgrade Tests // ============================================================================================= diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt index 81f94b161..0d5ea4e51 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt @@ -15,6 +15,7 @@ package com.firebase.ui.auth.configuration.auth_provider import android.content.Context +import androidx.compose.ui.test.junit4.createComposeRule import androidx.core.net.toUri import androidx.credentials.CredentialManager import androidx.test.core.app.ApplicationProvider @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -67,6 +69,9 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class GoogleAuthProviderFirebaseAuthUITest { + @get:Rule + val composeTestRule = createComposeRule() + @Mock private lateinit var mockFirebaseAuth: FirebaseAuth @@ -920,4 +925,59 @@ class GoogleAuthProviderFirebaseAuthUITest { val finalState = instance.authStateFlow().first { it !is AuthState.Loading } assertThat(finalState).isEqualTo(AuthState.Success(result = mockAuthResult, user = mockUser, isNewUser = false)) } + + // ============================================================================================= + // rememberGoogleSignInHandler - onSignInFailure reporting + // ============================================================================================= + + @Test + fun `rememberGoogleSignInHandler reports failure via onSignInFailure immediately, at the source`() { + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val googleProvider = AuthProvider.Google( + serverClientId = "test-client-id", + scopes = emptyList() + ) + val config = authUIConfiguration { + context = applicationContext + providers { + provider(googleProvider) + } + } + + // A picker-level failure that used to never reach onSignInFailure at all: + // the outer fallback throws AuthException.UnknownException when no Google accounts are found. + instance.testCredentialManagerProvider = object : AuthProvider.Google.CredentialManagerProvider { + override suspend fun getGoogleCredential( + context: Context, + credentialManager: CredentialManager, + serverClientId: String, + filterByAuthorizedAccounts: Boolean, + autoSelectEnabled: Boolean + ): AuthProvider.Google.GoogleSignInResult { + throw AuthException.UnknownException( + "No Google accounts available.\n\nPlease add a Google account to your device and try again." + ) + } + + override suspend fun clearCredentialState(context: Context, credentialManager: CredentialManager) = Unit + } + + val reportedFailures = mutableListOf() + var launcher: (() -> Unit)? = null + + composeTestRule.setContent { + launcher = instance.rememberGoogleSignInHandler( + context = applicationContext, + config = config, + provider = googleProvider, + onSignInFailure = { reportedFailures.add(it) }, + ) + } + + composeTestRule.runOnIdle { launcher?.invoke() } + composeTestRule.waitForIdle() + + assertThat(reportedFailures).hasSize(1) + assertThat(reportedFailures.single()).isInstanceOf(AuthException.UnknownException::class.java) + } } From c3c78bb0fe29421fe422245e28163b1264ec001d Mon Sep 17 00:00:00 2001 From: demolaf Date: Wed, 5 Aug 2026 12:29:07 +0100 Subject: [PATCH 2/3] fix(auth): avoid stale onSignInFailure/config closures in Google/Anonymous/OAuth/Facebook sign-in handlers --- .../AnonymousAuthProvider+FirebaseAuthUI.kt | 27 +++++++------- .../FacebookAuthProvider+FirebaseAuthUI.kt | 24 ++++++++----- .../GoogleAuthProvider+FirebaseAuthUI.kt | 25 ++++++------- .../OAuthProvider+FirebaseAuthUI.kt | 35 +++++++++---------- 4 files changed, 54 insertions(+), 57 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt index 060788a8a..f86c8683b 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt @@ -1,7 +1,6 @@ package com.firebase.ui.auth.configuration.auth_provider import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.firebase.ui.auth.AuthException import com.firebase.ui.auth.AuthState @@ -28,20 +27,18 @@ internal fun FirebaseAuthUI.rememberAnonymousSignInHandler( ): () -> Unit { val context = androidx.compose.ui.platform.LocalContext.current val coroutineScope = rememberCoroutineScope() - return remember(this) { - { - coroutineScope.launch { - try { - signInAnonymously(config) - } catch (e: AuthException) { - // Already an AuthException, don't re-wrap it - updateAuthState(AuthState.Error(e)) - onSignInFailure(e) - } catch (e: Exception) { - val authException = AuthException.from(e, context) - updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) - } + return { + coroutineScope.launch { + try { + signInAnonymously(config) + } catch (e: AuthException) { + // Already an AuthException, don't re-wrap it + updateAuthState(AuthState.Error(e)) + onSignInFailure(e) + } catch (e: Exception) { + val authException = AuthException.from(e, context) + updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index 445799018..cc60d7032 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -19,8 +19,10 @@ import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import com.facebook.AccessToken import com.facebook.CallbackManager import com.facebook.FacebookCallback @@ -64,6 +66,10 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( val coroutineScope = rememberCoroutineScope() val callbackManager = remember { CallbackManager.Factory.create() } val loginManager = LoginManager.getInstance() + val currentContext by rememberUpdatedState(context) + val currentConfig by rememberUpdatedState(config) + val currentProvider by rememberUpdatedState(provider) + val currentOnSignInFailure by rememberUpdatedState(onSignInFailure) val launcher = rememberLauncherForActivityResult( loginManager.createLogInActivityResultContract( @@ -73,7 +79,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( onResult = {}, ) - DisposableEffect(config) { + DisposableEffect(Unit) { loginManager.registerCallback( callbackManager, object : FacebookCallback { @@ -81,19 +87,19 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( coroutineScope.launch { try { signInWithFacebook( - context = context, - config = config, - provider = provider, + context = currentContext, + config = currentConfig, + provider = currentProvider, accessToken = result.accessToken, ) } catch (e: AuthException) { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) - onSignInFailure(e) + currentOnSignInFailure(e) } catch (e: Exception) { - val authException = AuthException.from(e, context) + val authException = AuthException.from(e, currentContext) updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) + currentOnSignInFailure(authException) } } } @@ -104,13 +110,13 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( override fun onError(error: FacebookException) { Log.e("FacebookAuthProvider", "Error during Facebook sign in", error) - val authException = AuthException.from(error, context) + val authException = AuthException.from(error, currentContext) updateAuthState( AuthState.Error( authException ) ) - onSignInFailure(authException) + currentOnSignInFailure(authException) } }) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt index 61be0fa9a..72ab67a1f 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt @@ -3,7 +3,6 @@ package com.firebase.ui.auth.configuration.auth_provider import android.content.Context import android.util.Log import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.credentials.CredentialManager import androidx.credentials.exceptions.GetCredentialException @@ -61,19 +60,17 @@ internal fun FirebaseAuthUI.rememberGoogleSignInHandler( onSignInFailure: (AuthException) -> Unit = {}, ): () -> Unit { val coroutineScope = rememberCoroutineScope() - return remember(this, config) { - { - coroutineScope.launch { - try { - signInWithGoogle(context, config, provider) - } catch (e: AuthException) { - updateAuthState(AuthState.Error(e)) - onSignInFailure(e) - } catch (e: Exception) { - val authException = AuthException.from(e, context) - updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) - } + return { + coroutineScope.launch { + try { + signInWithGoogle(context, config, provider) + } catch (e: AuthException) { + updateAuthState(AuthState.Error(e)) + onSignInFailure(e) + } catch (e: Exception) { + val authException = AuthException.from(e, context) + updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt index 7d6b02952..6d097251b 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt @@ -3,7 +3,6 @@ package com.firebase.ui.auth.configuration.auth_provider import android.app.Activity import android.content.Context import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.firebase.ui.auth.AuthException import com.firebase.ui.auth.AuthState @@ -63,24 +62,22 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler( "Ensure FirebaseAuthScreen is used within an Activity." ) - return remember(this, provider.providerId, config) { - { - coroutineScope.launch { - try { - signInWithProvider( - context = context, - config = config, - activity = activity, - provider = provider - ) - } catch (e: AuthException) { - updateAuthState(AuthState.Error(e)) - onSignInFailure(e) - } catch (e: Exception) { - val authException = AuthException.from(e, context) - updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) - } + return { + coroutineScope.launch { + try { + signInWithProvider( + context = context, + config = config, + activity = activity, + provider = provider + ) + } catch (e: AuthException) { + updateAuthState(AuthState.Error(e)) + onSignInFailure(e) + } catch (e: Exception) { + val authException = AuthException.from(e, context) + updateAuthState(AuthState.Error(authException)) + onSignInFailure(authException) } } } From bbae7c99774db21e99996bd115c0123f457e258d Mon Sep 17 00:00:00 2001 From: demolaf Date: Wed, 5 Aug 2026 14:45:34 +0100 Subject: [PATCH 3/3] fix(auth): don't report onSignInFailure for user-cancelled sign-in --- .../auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt | 4 ++-- .../auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt | 4 ++-- .../auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt | 4 ++-- .../auth_provider/OAuthProvider+FirebaseAuthUI.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt index f86c8683b..1027b9cab 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt @@ -34,11 +34,11 @@ internal fun FirebaseAuthUI.rememberAnonymousSignInHandler( } catch (e: AuthException) { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) - onSignInFailure(e) + if (e !is AuthException.AuthCancelledException) onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) + if (authException !is AuthException.AuthCancelledException) onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index cc60d7032..6cefb834a 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -95,11 +95,11 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( } catch (e: AuthException) { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) - currentOnSignInFailure(e) + if (e !is AuthException.AuthCancelledException) currentOnSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, currentContext) updateAuthState(AuthState.Error(authException)) - currentOnSignInFailure(authException) + if (authException !is AuthException.AuthCancelledException) currentOnSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt index 72ab67a1f..c8b51fcce 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt @@ -66,11 +66,11 @@ internal fun FirebaseAuthUI.rememberGoogleSignInHandler( signInWithGoogle(context, config, provider) } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) - onSignInFailure(e) + if (e !is AuthException.AuthCancelledException) onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) + if (authException !is AuthException.AuthCancelledException) onSignInFailure(authException) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt index 6d097251b..58d35cea0 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt @@ -73,11 +73,11 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler( ) } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) - onSignInFailure(e) + if (e !is AuthException.AuthCancelledException) onSignInFailure(e) } catch (e: Exception) { val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) - onSignInFailure(authException) + if (authException !is AuthException.AuthCancelledException) onSignInFailure(authException) } } }