What You'll Learn
Credential Manager (passkeys, password save/retrieve, Google Sign-In integration, Compose UI integration) explained.
Setup
dependencies {
implementation("androidx.credentials:credentials:1.5.0")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
}
CredentialRepository
class CredentialRepository @Inject constructor(
@ApplicationContext private val context: Context
) {
private val credentialManager = CredentialManager.create(context)
suspend fun signInWithSavedCredentials(activity: Activity): AuthResult {
val request = GetCredentialRequest.Builder()
.addCredentialOption(GetPasswordOption())
.addCredentialOption(
GetPublicKeyCredentialOption(
requestJson = createPasskeyRequestJson()
)
)
.addCredentialOption(
GetGoogleIdOption.Builder()
.setServerClientId(BuildConfig.GOOGLE_CLIENT_ID)
.setFilterByAuthorizedAccounts(false)
.build()
)
.build()
return try {
val result = credentialManager.getCredential(activity, request)
handleCredential(result.credential)
} catch (e: GetCredentialCancellationException) {
AuthResult.Cancelled
} catch (e: NoCredentialException) {
AuthResult.NoCredentials
} catch (e: Exception) {
AuthResult.Error(e.message ?: "Unknown error")
}
}
suspend fun savePassword(activity: Activity, email: String, password: String) {
val credential = CreatePasswordRequest(email, password)
credentialManager.createCredential(activity, credential)
}
private fun handleCredential(credential: Credential): AuthResult {
return when (credential) {
is PasswordCredential -> {
AuthResult.Success(credential.id, credential.password)
}
is PublicKeyCredential -> {
AuthResult.PasskeySuccess(credential.authenticationResponseJson)
}
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleId = GoogleIdTokenCredential.createFrom(credential.data)
AuthResult.GoogleSuccess(googleId.idToken)
} else {
AuthResult.Error("Unsupported credential type")
}
}
else -> AuthResult.Error("Unsupported credential")
}
}
}
sealed interface AuthResult {
data class Success(val email: String, val password: String) : AuthResult
data class PasskeySuccess(val responseJson: String) : AuthResult
data class GoogleSuccess(val idToken: String) : AuthResult
data object Cancelled : AuthResult
data object NoCredentials : AuthResult
data class Error(val message: String) : AuthResult
}
Compose Screen
@Composable
fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val activity = LocalContext.current as Activity
LaunchedEffect(Unit) {
viewModel.signInWithSavedCredentials(activity)
}
Column(
Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (uiState) {
LoginUiState.Loading -> CircularProgressIndicator()
is LoginUiState.Error -> {
Text((uiState as LoginUiState.Error).message, color = MaterialTheme.colorScheme.error)
Spacer(Modifier.height(16.dp))
Button(onClick = { viewModel.signInWithSavedCredentials(activity) }) {
Text("Sign In")
}
}
LoginUiState.NeedInput -> {
Button(
onClick = { viewModel.signInWithSavedCredentials(activity) },
modifier = Modifier.fillMaxWidth()
) { Text("Sign In with Saved Credentials") }
}
LoginUiState.Success -> {
Text("Sign In Successful!", style = MaterialTheme.typography.headlineMedium)
}
}
}
}
Summary
| Feature | Implementation |
|---|---|
| Password retrieve | GetPasswordOption |
| Passkeys | GetPublicKeyCredentialOption |
| Google Sign-In | GetGoogleIdOption |
| Save password | CreatePasswordRequest |
-
CredentialManagerunified auth management - Passkeys/password/Google Sign-In presented together
- User chooses preferred auth method
- Android 14+ system integration for best UX
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)