Best Website for Jetpack Compose App Development

Android Jetpack Compose

Stay ahead with the latest tools, trends, and best practices in Android Jetpack Compose App development

Biometric Authentication in Jetpack Compose Android 16

Biometric Authentication in Jetpack Compose Android 16 - Responsive Blogger Template
Biomedic Authentication in Jetpack Compose

Biometric Authentication in Android 16 

Biometric Authentication in Jetpack Compose (Fingerprint & Face Login)

In today’s mobile-first world, security is not optional—it is an expectation. Users want apps that are fast, convenient, and secure without forcing them to remember complex passwords. This is where biometric authentication becomes a game changer.

Android has supported biometrics such as fingerprint, face recognition, and iris scanning for years. However, with the rise of Jetpack Compose, Android’s modern declarative UI toolkit, developers often ask:

How do we implement Biometric Authentication in Jetpack Compose (Fingerprint & Face Login)?

This article answers that question deeply—not just with code, but with conceptual clarity, real-world architecture, and best practices. By the end, you will understand how biometrics work, why Compose changes the implementation approach, and how to build a secure, user-friendly biometric login flow.

What Is Biometric Authentication?

Biometric authentication verifies a user’s identity using biological traits, such as:

Unlike passwords:

  • Biometrics cannot be forgotten
  • They are harder to replicate
  • They offer faster authentication

Android provides a unified API through BiometricPrompt, which abstracts device-specific implementations and ensures consistent behavior.

Why Biometrics Matter in Modern Apps

Let’s understand the why before the how.

Problems with Traditional Authentication

  • Users forget passwords
  • Weak passwords reduce security
  • Frequent logins hurt user experience
  • Password reuse increases attack risk

Advantages of Biometrics

✅ Faster login

✅ Better UX

✅ Stronger security

✅ Less friction

✅ Trusted by users

That’s why banking apps, payment apps, and secure enterprise apps rely heavily on biometric authentication.

Jetpack Compose and Authentication: A Paradigm Shift

Jetpack Compose is declarative, while biometric APIs are imperative and callback-based. This mismatch creates confusion for many developers.

Key Differences

XML-based UIJetpack Compose
UI updated manuallyUI reacts to state
Callbacks modify viewsState drives UI
Lifecycle tied to ViewsLifecycle-aware composables
πŸ‘‰ Core Rule:
In Compose, biometric authentication should update state, not UI elements directly.

Before writing code, you must understand the components involved.

1. BiometricManager

Checks:

Whether biometric hardware exists


2. BiometricPrompt

Responsible for:

Showing the biometric dialog

Handling success, failure, and errors

3. Executor

Ensures callbacks run on the main thread.

Real-World Authentication Flow

A well-designed biometric flow looks like this:
  1. User opens app
  2. App checks biometric availability
  3. If available → show biometric prompt
  4. On success → unlock app
  5. On failure → retry or fallback
  6. On error → show message

What is biometric authentication in Android?

Biometric authentication is a security mechanism that verifies a user’s identity using biological traits such as fingerprint, face recognition, or iris scan. Android provides this feature through the Biometric API, which securely interacts with device hardware and system UI.

This tutorial follows real-world app behavior, not beginner shortcuts.


🧭 Navigation Structure

Auth ScreenHome Screen

We use Jetpack Navigation Compose to protect the Home screen behind biometric authentication.

@Composable
fun BiometricAuthDemo() {
    val navController = rememberNavController()

    NavHost(navController, startDestination = "auth") {

        composable("auth") {
            AuthGateScreen(
                viewModel = viewModel(),
                onNavigateHome = {
                    navController.navigate("home") {
                        popUpTo("auth") { inclusive = true }
                    }
                }
            )
        }

        composable("home") {
            HomeScreen()
        }
    }
}

Why popUpTo?
To prevent users from pressing the back button and returning to the auth screen. This is a security best practice.


🧠 Why We Use ViewModel (Not remember)

❌ Beginner mistake:

var isAuthenticated by remember { mutableStateOf(false) }

Problems:

  • State resets on screen rotation
  • Logic mixed with UI
  • Not testable

✅ Correct approach:

class AuthViewModel : ViewModel() {

    private val _state = MutableStateFlow<AuthState>(AuthState.Idle)
    val state: StateFlow<AuthState> = _state

    fun startAuth() {
        _state.value = AuthState.Authenticating
    }

    fun onSuccess() {
        _state.value = AuthState.Success
    }

    fun onFailure() {
        _state.value = AuthState.Failed
    }

    fun onError(message: String) {
        _state.value = AuthState.Error(message)
    }
}

This is called state-driven authentication.


πŸ“¦ Understanding AuthState (Very Important)

sealed class AuthState {
    object Idle : AuthState()
    object Authenticating : AuthState()
    object Success : AuthState()
    object Failed : AuthState()
    data class Error(val message: String) : AuthState()
}

Each state represents a real situation in the app:

  • Idle → App just opened
  • Authenticating → Show biometric prompt
  • Success → Navigate to Home
  • Failed → Retry authentication
  • Error → Show error message

⚙ Triggering Biometric Safely Using LaunchedEffect

Biometric prompt is a side-effect, so it must not be triggered directly in UI.

LaunchedEffect(state) {
    when (state) {

        AuthState.Idle -> viewModel.startAuth()

        AuthState.Authenticating -> biometricAuthenticator.authenticate()

        AuthState.Failed -> biometricAuthenticator.authenticate()

        AuthState.Success -> onNavigateHome()

        else -> {}
    }
}

Why LaunchedEffect?

  • Prevents multiple biometric dialogs
  • Runs only when state changes
  • Safe with recomposition

🧩 Why FragmentActivity Is Required (Most Important Concept)

Many beginners ask: "Why not ComponentActivity?"

Answer:
BiometricPrompt internally depends on Fragments.

That’s why this is correct:

class MainActivity : FragmentActivity()

❌ This can crash or behave incorrectly:

class MainActivity : ComponentActivity()

Rule:
If an Android API internally uses fragments → use FragmentActivity.


πŸ” BiometricAuthenticator Class (Clean Architecture)

class BiometricAuthenticator(
    activity: FragmentActivity,
    private val viewModel: AuthViewModel
) {

    private val executor = ContextCompat.getMainExecutor(activity)

    private val prompt = BiometricPrompt(
        activity,
        executor,
        object : BiometricPrompt.AuthenticationCallback() {

            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult
            ) {
                viewModel.onSuccess()
            }

            override fun onAuthenticationFailed() {
                viewModel.onFailure()
            }

            override fun onAuthenticationError(
                errorCode: Int,
                errString: CharSequence
            ) {
                viewModel.onError(errString.toString())
            }
        }
    )

    fun authenticate() {
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate")
            .setAllowedAuthenticators(
                BiometricManager.Authenticators.BIOMETRIC_STRONG or
                BiometricManager.Authenticators.DEVICE_CREDENTIAL
            )
            .build()

        prompt.authenticate(promptInfo)
    }
}

This separation keeps:

  • UI clean
  • Logic testable
  • Code production-ready

🏠 Home Screen (After Authentication)

Home screen does NOT care how authentication happened. It only shows secured content.

@Composable
fun HomeScreen() {
    Column {
        Text("Home Screen")
        Text("You are successfully authenticated")
    }
}

❌ Common Beginner Mistakes

  • Calling biometric directly inside Composable
  • Using remember instead of ViewModel
  • Not handling back navigation
  • Using ComponentActivity
  • No retry logic

Your implementation avoids all of these mistakes.

Final Code:

 // Main Activity
class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ComposeAppTheme {
                /*Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }*/
              

                BiometricAuthDemo()
            }
        }
    }
}

 // BiometricAuthDemo Screen
@Composable
fun BiometricAuthDemo() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "auth") {

        composable("auth") {
            AuthGateScreen(
                viewModel = viewModel(),
                onNavigateHome = {
                    navController.navigate("home") {
                        popUpTo("auth") { inclusive = true }
                    }
                }
            )
        }

        composable("home") {
            HomeScreen()
        }
    }
}
 // First Screen (AuthGateScreen)

@Composable
fun AuthGateScreen(
    viewModel: AuthViewModel,
    onNavigateHome: () -> Unit
) {

    val context = LocalContext.current
    val activity = context as? FragmentActivity ?: return

  //  val activity = context as FragmentActivity
    val state by viewModel.state.collectAsState()

    val biometricAuthenticator = remember {
        BiometricAuthenticator(activity, viewModel)
    }

    // πŸ” Trigger authentication based on state
    LaunchedEffect(state) {
        delay(300)
        when (state) {

            AuthState.Idle -> {
                viewModel.startAuth()
            }

            AuthState.Authenticating -> {
                biometricAuthenticator.authenticate()
            }

            AuthState.Failed -> {
                biometricAuthenticator.authenticate()
            }

            AuthState.Success -> {
                onNavigateHome()
            }

            else -> {}
        }
    }

    // Optional UI
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Authenticating...")
    }
}

// Second Screen (HomeScreen)

@Composable
fun HomeScreen(
    onLogout: () -> Unit = {}
) {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(24.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            Text(
                text = "🏠 Home Screen",
                style = MaterialTheme.typography.headlineLarge
            )

            Spacer(modifier = Modifier.height(12.dp))

            Text(
                text = "You are successfully authenticated",
                style = MaterialTheme.typography.bodyLarge
            )

            Spacer(modifier = Modifier.height(32.dp))

            Button(
                onClick = onLogout
            ) {
                Text("Logout")
            }
        }
    }
}

 // Biometric Authenticator Class
 
class BiometricAuthenticator(
    activity: FragmentActivity,
    private val viewModel: AuthViewModel
) {

    private val executor = ContextCompat.getMainExecutor(activity)

    private val prompt = BiometricPrompt(
        activity,
        executor,
        object : BiometricPrompt.AuthenticationCallback() {

            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult
            ) {
                viewModel.onSuccess()
            }

            override fun onAuthenticationFailed() {
                viewModel.onFailure()
            }

            override fun onAuthenticationError(
                errorCode: Int,
                errString: CharSequence
            ) {
                viewModel.onError(errString.toString())
            }
        }
    )

    fun authenticate() {
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate")
            .setAllowedAuthenticators(
                BiometricManager.Authenticators.BIOMETRIC_STRONG or
                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
            )
            .build()

        prompt.authenticate(promptInfo)
    }
}

// Fragment Class


class BiometricFragment(
    private val onResult: (AuthState) -> Unit
) : Fragment() {

    fun authenticate() {
        val executor = ContextCompat.getMainExecutor(requireContext())

        val biometricPrompt = BiometricPrompt(
            this, // ✅ Fragment!
            executor,
            object : BiometricPrompt.AuthenticationCallback() {

                override fun onAuthenticationSucceeded(
                    result: BiometricPrompt.AuthenticationResult
                ) {
                    onResult(AuthState.Success)
                }

                override fun onAuthenticationFailed() {
                    onResult(AuthState.Failed)
                }

                override fun onAuthenticationError(
                    errorCode: Int,
                    errString: CharSequence
                ) {
                    if (errorCode != BiometricPrompt.ERROR_CANCELED) {
                        onResult(AuthState.Failed)
                    }
                }
            }
        )

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate")
            .setSubtitle("Use fingerprint or face")
            .setAllowedAuthenticators(
                BiometricManager.Authenticators.BIOMETRIC_STRONG or
                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
            )
            .build()

        biometricPrompt.authenticate(promptInfo)
    }
}

// Sealed Class

sealed class AuthState {
    object Idle : AuthState()
    object Authenticating : AuthState()
    object Success : AuthState()
    object Failed : AuthState()
    data class Error(val message: String) : AuthState()
}

// AuthViewModel


class AuthViewModel : ViewModel() {

    private val _state = MutableStateFlow(AuthState.Idle)
    val state: StateFlow = _state

    fun startAuth() {
        _state.value = AuthState.Authenticating
    }

    fun onSuccess() {
        _state.value = AuthState.Success
    }

    fun onFailure() {
        _state.value = AuthState.Failed
    }

    fun onError(message: String) {
        _state.value = AuthState.Error(message)
    }
}

Required Dependecies

implementation("androidx.biometric:biometric:1.2.0-alpha05")
    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")

Output:

Biometric Authentication in Android 16 Authentication Screen

Biometric authentication in Jetpack Compose using fingerprint login

Biometric authentication in Jetpack Compose using fingerprint login


❓ Frequently Asked Questions (FAQ)

Biometric Authentication in Jetpack Compose

1. What is biometric authentication in Android?

Biometric authentication is a security mechanism that verifies a user’s identity using biological traits such as fingerprint, face recognition, or iris scan. Android provides this feature through the Biometric API, which securely interacts with device hardware and system UI.

2. Does Jetpack Compose have a built-in biometric component?

❌ No.
Jetpack Compose does not provide a biometric UI component. Instead, it integrates with Android’s BiometricPrompt API, which displays a system-controlled authentication dialog.

Compose handles the UI state, while biometric authentication is handled outside the UI layer.

3. Why is BiometricPrompt preferred over older fingerprint APIs?

BiometricPrompt is preferred because:
  • It supports multiple biometric types
  • It provides a consistent UI
  • It handles security and hardware differences
  • It supports device credential fallback
  • Older APIs like FingerprintManager are deprecated

4. Can biometric authentication work without fingerprint hardware?

✅ Yes.
If you enable DEVICE_CREDENTIAL, authentication can fall back to:
  • PIN
  • Pattern
  • Password
This ensures authentication works even on devices without biometric sensors.

5. What is the correct way to use biometrics in Jetpack Compose?

The correct approach is:
  • Keep biometric logic outside composables
  • Use a helper or ViewModel
  • Update state, not UI
  • Let Compose react to state changes
πŸ‘‰ Compose = State → UI

6. Why should biometric logic not be written inside composables?

Because composables:
  • Can recompose multiple times
  • Should remain side-effect free
  • Are not lifecycle-owners
Putting biometric logic inside composables can cause:
❌ Multiple dialogs
❌ Memory leaks
❌ Crashes

7. What role does ViewModel play in biometric authentication?

ViewModel:
  • Stores authentication state
  • Survives configuration changes
  • Separates UI from business logic
  • Makes the app testable and scalable

8. What is the purpose of BiometricManager?

BiometricManager checks:
  • If biometric hardware exists
  • If biometrics are enrolled
  • If authentication is allowed on the device
It helps you decide whether to show biometric login or fallback UI.

9. Is biometric data accessible to the app?

❌ No.
Android stores biometric data in secure hardware (TEE or StrongBox).
Apps never receive or store biometric information—only authentication results.

10. Is biometric authentication completely secure?

Biometrics are very secure, but best practice is to:
  • Combine biometrics with encrypted storage
  • Re-authenticate for sensitive actions
  • Avoid using biometrics as the only security layer

11. What happens if biometric authentication fails?

The system remains secure
  • The user can retry
  • The app receives a failure callback
  • No sensitive data is exposed
Failure does not mean a security breach.

12. How should errors be handled in biometric authentication?

Errors may occur due to:
  • Too many failed attempts
  • User cancellation
  • Hardware issues
Best practice:
  • Show a clear message
  • Allow retry
  • Provide fallback authentication

13. Can biometric authentication be used for auto-login?

✅ Yes, but with caution.

Recommended flow:
  • Use biometrics to unlock a secure token
  • Never bypass authentication completely
  • Re-authenticate after app restart or timeout

14. Should biometric authentication be mandatory?

❌ No.

Best UX practice:
  • Offer biometrics as an option
  • Allow users to disable it
  • Always provide a fallback login method

15. How do I test biometric authentication in emulator?

Steps:
  • Run the emulator
  • Open Extended Controls
  • Go to Fingerprint
  • Tap Touch Sensor
This simulates fingerprint authentication.

16. Does biometric authentication work on all Android versions?

❌ No, biometric authentication does not work the same way on all Android versions.

Biometric authentication support depends on the Android version and the biometric APIs available on that version.

  • Android 6.0 (API 23) to Android 9 (API 28)
    ❌ No official Biometric API.
    Fingerprint authentication was available using the old FingerprintManager (now deprecated).
  • Android 10 (API 29)
    ✅ Introduced BiometricPrompt, but with limited options.
    Mostly supported fingerprint authentication.
  • Android 11 (API 30) and above
    ✅ Full support for modern biometric authentication.
    Supports fingerprint, face unlock, and device credentials (PIN, pattern).

Best Practice (What professionals do):

BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL

This ensures:

  • Compatibility with most Android versions
  • Fallback to device PIN or pattern
  • Better user experience

Important Note:
Even on supported Android versions, biometric authentication will NOT work if:

  • No fingerprint or face is enrolled on the device
  • Device hardware does not support biometrics
  • Biometric authentication is disabled in system settings

Conclusion:
Biometric authentication works best on Android 10+, and for real apps, you should always provide a device credential fallback to support all users.

17. What is BIOMETRIC_STRONG?

BIOMETRIC_STRONG ensures:
  • High-security biometrics
  • Resistant to spoofing
  • Required for financial and enterprise apps
Examples:
  • Fingerprint
  • Secure face unlock

18. Can biometric authentication be used with EncryptedSharedPreferences?

✅ Yes (Recommended).

Flow:
  • Authenticate user
  • Unlock encryption key
  • Access sensitive data securely
This is widely used in banking apps.

19. What are common mistakes developers make?

❌ No fallback authentication
❌ UI logic inside callbacks
❌ Calling biometric prompt on recomposition
❌ Not handling lifecycle
❌ Hardcoding strings

20. Is biometric authentication suitable for all apps?

Biometrics are best for:
  • Banking apps
  • Finance apps
  • Secure dashboards
  • Admin panels
  • Private data apps
Not ideal for:
  • Casual games
  • Public content apps

21. Can biometric authentication be reused for multiple screens?

✅ Yes.

Best approach:
  • Centralize logic in a helper or ViewModel
  • Trigger authentication when needed
  • Use navigation guards

22. How does Compose recomposition affect biometrics?

Recomposition does not affect authentication if:
  • You avoid side effects
  • You use state properly
  • You trigger authentication explicitly

23. Is biometric authentication accessible?

Yes, because:
  • System UI handles accessibility
  • Supports screen readers
  • Provides fallback credentials

24. Can biometric authentication fail permanently?

Only temporarily (e.g., too many attempts).
The system automatically unlocks after a cooldown or fallback login.

25. What is the best architecture for biometrics in Compose?

✅ MVVM
✅ StateFlow
✅ Clean separation
✅ Lifecycle-aware logic

This ensures scalability and maintainability.

🎯 Final Thoughts

✔ Biometric authentication is secure and user-friendly
✔ Jetpack Compose handles UI through state
✔ Android handles security through system APIs
✔ Best practices ensure reliability and trust

If you want to build secure, professional Android apps, biometric authentication must be:

  • State-driven
  • Lifecycle-aware
  • Separated from UI
  • Handled by FragmentActivity

This architecture is used in banking apps, payment apps, and enterprise apps.

SkillDedication

— Python High Level Programming Language- Expert-Written Tutorials, Projects, and Tools—

Special Message

Welcome To Coding BiharπŸ‘¨‍🏫