signin: add cross-platform biometric auth flow#715
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed3908fafe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| factoryOf<AppVersionUseCase, Context>(constructor = ::AppVersionUseCase) | ||
|
|
||
| actual fun Module.singleOfBiometricAuthService(): KoinDefinition<BiometricAuthService> = | ||
| factoryOf<BiometricAuthService, Context>(constructor = ::AndroidBiometricAuthService) |
There was a problem hiding this comment.
Inject an Activity context for biometric auth
This binding creates AndroidBiometricAuthService from Koin's generic Context, but Android Koin is initialized with the application context (androidContext(this@MainApplication)), not an Activity. As a result, authenticate() cannot obtain a FragmentActivity and immediately returns FallbackToPassword, so Android users will never see a biometric prompt even when isBiometricAvailable() is true.
Useful? React with 👍 / 👎.
| override fun onAuthenticationFailed() { | ||
| if (continuation.isActive) { | ||
| continuation.resume(BiometricAuthResult.Failed) | ||
| } |
There was a problem hiding this comment.
Do not finish auth flow on non-terminal biometric failure
onAuthenticationFailed() is a non-terminal callback (e.g., one bad fingerprint while the prompt remains open), but this code resumes the coroutine as Failed immediately. That ends the sign-in flow on the first mismatch and can ignore a later successful scan from the same prompt session, producing incorrect login failures.
Useful? React with 👍 / 👎.
| showBiometricButton = signInResultState.value in setOf( | ||
| SignInResult.ShowBiometricAvailable, | ||
| SignInResult.ShowBiometricInProgress, | ||
| SignInResult.ShowBiometricSuccess, | ||
| SignInResult.ShowBiometricFailed, |
There was a problem hiding this comment.
Preserve biometric button visibility across password errors
The biometric button is shown only for a subset of biometric-specific SignInResult values; after any password attempt, SignInViewModel switches state to ShowEmptyPassError, ShowIncorrectPassError, or ShowSignInForm, which hides the button even on devices where biometrics are available. This traps users in password-only mode unless the screen is recreated.
Useful? React with 👍 / 👎.
Motivation
Description
BiometricAuthServiceand result sealed typeBiometricAuthResultincore/presentationand implement platform-specific providers for Android (AndroidBiometricAuthService), iOS (IosBiometricAuthService), JVM (JvmBiometricAuthService), and Wasm (WasmJsBiometricAuthService).singleOfBiometricAuthService()expect/actual hook and register actual implementations in each platform module, and addandroidx.biometricto dependencies for the Android target.BiometricAuthServiceintoSignInViewModel, addOnBiometricClickaction and newSignInResultstates (available, in-progress, success, failed, fallback-to-password), and implement availability check and biometric authentication flow that navigates toAppNavGraph.Mainon success.SignInScreento show a biometric button only when biometric auth is available, add UI strings and a test tag (SIGN_IN_BIOMETRIC_BUTTON_TAG), and route biometric failures to the existing password path.Testing
./gradle/build_quick.sh,./gradlew :app:android:connectedCheck, and./gradlew build, but all three failed in this environment before project tasks could execute due to plugin resolution errors (pluginorg.gradle.kotlin.kotlin-dsl:6.5.2could not be resolved).SignInViewModelTestwas updated to mockBiometricAuthServiceand exercise existing password flows locally during development.Codex Task