Skip to content
Open
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 @@ -88,6 +88,9 @@ class MethodPickerTermsConfiguration(
* @param lastSignInPreference The last sign-in preference to show a "Continue as..." button.
* @param termsConfiguration Optional configuration for a custom ToS/Privacy Policy footer.
* When provided, replaces the default "By continuing..." text. See [MethodPickerTermsConfiguration].
* @param onContinueAsSelected A callback when the "Continue as..." button is selected, with the
* provider and saved identifier (email address). Falls back to [onProviderSelected]
* if not provided.
Comment thread
just1and0 marked this conversation as resolved.
*
* @since 10.0.0
*/
Expand All @@ -102,7 +105,10 @@ fun AuthMethodPicker(
lastSignInPreference: SignInPreferenceManager.SignInPreference? = null,
customLayout: (@Composable (List<AuthProvider>, (AuthProvider) -> Unit) -> Unit)? = null,
termsConfiguration: MethodPickerTermsConfiguration? = null,
onContinueAsSelected: ((AuthProvider, String?) -> Unit)? = null,
) {
val continueAsHandler: (AuthProvider, String?) -> Unit =
onContinueAsSelected ?: { provider, _ -> onProviderSelected(provider) }
val context = LocalContext.current
val inPreview = LocalInspectionMode.current
val stringProvider = LocalAuthUIStringProvider.current
Expand Down Expand Up @@ -150,7 +156,7 @@ fun AuthMethodPicker(
provider = lastProvider,
identifier = preference.identifier,
enabled = providerButtonsEnabled,
onClick = { onProviderSelected(lastProvider) }
onClick = { continueAsHandler(lastProvider, preference.identifier) }
)
Spacer(modifier = Modifier.height(24.dp))

Expand Down Expand Up @@ -218,7 +224,7 @@ fun AuthMethodPicker(
* A prominent "Continue as..." button that shows the last-used provider and identifier.
*
* @param provider The authentication provider
* @param identifier The user identifier (email, phone number, etc.)
* @param identifier The user identifier (email address)
* @param onClick Callback when the button is clicked
*/
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ fun FirebaseAuthScreen(
val pendingReauthState = remember { mutableStateOf<AuthState.ReauthenticationRequired?>(null) }
val pendingReauthOperation = remember { mutableStateOf<(suspend (android.content.Context) -> Unit)?>(null) }
val emailLinkFromDifferentDevice = remember { mutableStateOf<String?>(null) }
val prefillEmail = remember { mutableStateOf<String?>(null) }
val lastSignInPreference =
remember { mutableStateOf<SignInPreferenceManager.SignInPreference?>(null) }
val startRoute = remember(configuration.providers, configuration.isProviderChoiceAlwaysShown) {
Expand Down Expand Up @@ -231,7 +232,14 @@ fun FirebaseAuthScreen(
privacyPolicyUrl = configuration.privacyPolicyUrl,
lastSignInPreference = lastSignInPreference.value,
termsConfiguration = customMethodPickerTermsConfiguration,
onProviderSelected = onProviderSelected,
onProviderSelected = { provider ->
prefillEmail.value = null
onProviderSelected(provider)
},
onContinueAsSelected = { provider, identifier ->
prefillEmail.value = if (provider is AuthProvider.Email) identifier else null
onProviderSelected(provider)
},
)
}
}
Expand All @@ -242,6 +250,7 @@ fun FirebaseAuthScreen(
context = context,
configuration = configuration,
authUI = authUI,
prefillEmail = prefillEmail.value,
credentialForLinking = pendingLinkingCredential.value,
emailLinkFromDifferentDevice = emailLinkFromDifferentDevice.value,
onContinueWithProvider = continueWithProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ fun EmailAuthScreen(
onSuccess: (AuthResult) -> Unit,
onError: (AuthException) -> Unit,
onCancel: () -> Unit,
prefillEmail: String? = null,
content: @Composable ((EmailAuthContentState) -> Unit)? = null,
) {
val provider = configuration.providers.filterIsInstance<AuthProvider.Email>().first()
Expand All @@ -150,7 +151,7 @@ fun EmailAuthScreen(
}
val mode = rememberSaveable { mutableStateOf(initialMode) }
val displayNameValue = rememberSaveable { mutableStateOf("") }
val emailTextValue = rememberSaveable { mutableStateOf("") }
val emailTextValue = rememberSaveable { mutableStateOf(prefillEmail ?: "") }
Comment thread
just1and0 marked this conversation as resolved.
val passwordTextValue = rememberSaveable { mutableStateOf("") }
val confirmPasswordTextValue = rememberSaveable { mutableStateOf("") }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2025 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.firebase.ui.auth.ui.screens.email

import android.content.Context
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import com.firebase.ui.auth.configuration.authUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@Config(sdk = [34])
@RunWith(RobolectricTestRunner::class)
class SignInUITest {

@get:Rule
val composeTestRule = createComposeRule()

private lateinit var applicationContext: Context
private lateinit var stringProvider: AuthUIStringProvider

@Before
fun setUp() {
applicationContext = ApplicationProvider.getApplicationContext()
stringProvider = DefaultAuthUIStringProvider(applicationContext)
}

@Test
fun `email field is pre-filled when initial email value is provided`() {
val prefillEmail = "user@example.com"
val provider = AuthProvider.Email(
isDisplayNameRequired = false,
emailLinkActionCodeSettings = null,
passwordValidationRules = emptyList()
)
val configuration = authUIConfiguration {
context = applicationContext
providers { provider(provider) }
}

composeTestRule.setContent {
CompositionLocalProvider(LocalAuthUIStringProvider provides stringProvider) {
SignInUI(
configuration = configuration,
isLoading = false,
emailSignInLinkSent = false,
email = prefillEmail,
password = "",
onEmailChange = { },
onPasswordChange = { },
onRetrievedCredential = { },
onSignInClick = { },
onGoToSignUp = { },
onGoToResetPassword = { },
onGoToEmailLinkSignIn = { },
)
}
}

composeTestRule.onNodeWithText(prefillEmail).assertExists()
}

@Test
fun `email field is empty when no initial email value is provided`() {
val provider = AuthProvider.Email(
isDisplayNameRequired = false,
emailLinkActionCodeSettings = null,
passwordValidationRules = emptyList()
)
val configuration = authUIConfiguration {
context = applicationContext
providers { provider(provider) }
}

composeTestRule.setContent {
CompositionLocalProvider(LocalAuthUIStringProvider provides stringProvider) {
SignInUI(
configuration = configuration,
isLoading = false,
emailSignInLinkSent = false,
email = "",
password = "",
onEmailChange = { },
onPasswordChange = { },
onRetrievedCredential = { },
onSignInClick = { },
onGoToSignUp = { },
onGoToResetPassword = { },
onGoToEmailLinkSignIn = { },
)
}
}

composeTestRule.onNodeWithText("user@example.com").assertDoesNotExist()
}
}