package com.example.myapp.utils
import android.content.Context
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BiometricAuthManager @Inject constructor(
private val context: Context
) {
fun canAuthenticate(): BiometricStatus {
val biometricManager = BiometricManager.from(context)
return when (biometricManager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)) {
BiometricManager.BIOMETRIC_SUCCESS ->
BiometricStatus.AVAILABLE
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
BiometricStatus.NO_HARDWARE
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
BiometricStatus.HW_UNAVAILABLE
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
BiometricStatus.NONE_ENROLLED
else -> BiometricStatus.UNKNOWN_ERROR
}
}
fun showBiometricPrompt(
activity: FragmentActivity,
title: String,
subtitle: String? = null,
description: String? = null,
onSuccess: () -> Unit,
onError: (String) -> Unit,
onFailure: () -> Unit = {}
) {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
when (errorCode) {
BiometricPrompt.ERROR_USER_CANCELED,
BiometricPrompt.ERROR_CANCELED ->
onError("Authentication cancelled")
BiometricPrompt.ERROR_LOCKOUT ->
onError("Too many attempts. Try again later.")
BiometricPrompt.ERROR_LOCKOUT_PERMANENT ->
onError("Biometric authentication disabled")
else ->
onError(errString.toString())
}
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
onSuccess()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailure()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(title)
.apply {
subtitle?.let { setSubtitle(it) }
description?.let { setDescription(it) }
}
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
.build()
biometricPrompt.authenticate(promptInfo)
}
}
enum class BiometricStatus {
AVAILABLE,
NO_HARDWARE,
HW_UNAVAILABLE,
NONE_ENROLLED,
UNKNOWN_ERROR
}
package com.example.myapp.ui.auth
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapp.utils.BiometricAuthManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class AuthViewModel @Inject constructor(
private val biometricAuthManager: BiometricAuthManager,
private val repository: AuthRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(AuthUiState())
val uiState: StateFlow<AuthUiState> = _uiState
init {
checkBiometricAvailability()
}
private fun checkBiometricAvailability() {
val status = biometricAuthManager.canAuthenticate()
_uiState.update {
it.copy(biometricStatus = status)
}
}
fun onBiometricAuthSuccess() {
viewModelScope.launch {
_uiState.update { it.copy(isAuthenticating = true) }
repository.loginWithBiometric()
.onSuccess { user ->
_uiState.update {
it.copy(
isAuthenticated = true,
isAuthenticating = false,
currentUser = user
)
}
}
.onFailure { error ->
_uiState.update {
it.copy(
isAuthenticating = false,
error = error.message
)
}
}
}
}
fun onBiometricAuthError(message: String) {
_uiState.update { it.copy(error = message) }
}
fun clearError() {
_uiState.update { it.copy(error = null) }
}
}
data class AuthUiState(
val isAuthenticated: Boolean = false,
val isAuthenticating: Boolean = false,
val biometricStatus: BiometricStatus = BiometricStatus.UNKNOWN_ERROR,
val currentUser: User? = null,
val error: String? = null
)
BiometricPrompt provides secure authentication via fingerprint, face, or iris. I create BiometricPrompt with callback handling success, error, and failure. PromptInfo configures title, subtitle, description, and allowed authenticators. Negative button text or setDeviceCredentialAllowed enables PIN/pattern fallback. CryptoObject secures operations with authenticated keys. Authentication types—BIOMETRICSTRONG, BIOMETRICWEAK, DEVICE_CREDENTIAL—define security levels. Error codes indicate why authentication failed. The API works across Android versions with androidx.biometric library. Biometric auth protects sensitive operations—payments, data access, logins. It provides convenient security without passwords, respecting user privacy and hardware capabilities.