I am using androidx.biometric:biometric:1.0.1
everything works fine but when I have a device without a biometric sensor (or when the user didn't set his fingerprint or etc) and I try to use DeviceCredentials after doing authentication my function input data is not valid.
class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.name
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.first).setOnClickListener {
authenticate(MyData(1, "first"))
}
findViewById<View>(R.id.second).setOnClickListener {
authenticate(MyData(2, "second"))
}
}
private fun authenticate(data: MyData) {
Log.e(TAG, "starting auth with $data")
val biometricPrompt = BiometricPrompt(
this,
ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Log.e(TAG, "auth done : $data")
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setDeviceCredentialAllowed(true)
.setTitle("title")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
data class MyData(
val id: Int,
val text: String
)
First I click on my first
button, authenticate, then I click my second
button and authenticate, then android logcat is like this:
E/com.test.biometrictest.MainActivity: starting auth with MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: starting auth with MyData(id=2, text=second)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)
as you see in last line MyData id and text is invalid! autneticate
function input(data) is not the same when onAuthenticationSucceeded is called!
(if you try to test it be sure to use DeviceCredentials not biometrics, I mean pattern or password, unset your fingerprint) Why data is not valid in callBack?
it works ok on android 10 or with fingerprint
I don`t want to use onSaveInstanceState.
When you create a new instance of BiometricPrompt
class, it adds a LifecycleObserver
to the activity and as I figured out it never removes it. So when you have multiple instances of BiometricPrompt
in an activity, there are multiple LifecycleObserver
at the same time that cause this issue.
For devices prior to Android Q, there is a transparent activity named DeviceCredentialHandlerActivity
and a bridge class named DeviceCredentialHandlerBridge
which support device credential authentication. BiometricPrompt
manages the bridge in different states and finally calls the callback methods in the onResume
state (when back to the activity after leaving credential window) if needed. When there are multiple LifecycleObserver
, The first one will handle the result and reset the bridge, so there is nothing to do by other observers. This the reason that the first callback implementation calls twice in your code.
Solution:
You should remove LifecycleObserver
from activity when you create a new instance of BiometricPrompt
class. Since there is no direct access to the observer, you need use reflection here. I modified your code based on this solution as below:
class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.name
private var lastLifecycleObserver: LifecycleObserver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.first).setOnClickListener {
authenticate(MyData(1, "first"))
}
findViewById<View>(R.id.second).setOnClickListener {
authenticate(MyData(2, "second"))
}
}
private fun authenticate(data: MyData) {
Log.e(TAG, "starting auth with $data")
lastLifecycleObserver?.let {
lifecycle.removeObserver(it)
lastLifecycleObserver = null
}
val biometricPrompt = BiometricPrompt(
this,
ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Log.e(TAG, "auth done : $data")
}
})
var field = BiometricPrompt::class.java.getDeclaredField("mLifecycleObserver")
field.isAccessible = true
lastLifecycleObserver = field.get(biometricPrompt) as LifecycleObserver
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setDeviceCredentialAllowed(true)
.setTitle("title")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
data class MyData(
val id: Int,
val text: String
)