Fixed
Status Update
Comments
le...@gmail.com <le...@gmail.com> #2
Hi Ed, Thank you so much for these suggestions. I've been reviewing them and merging them in. Hopefully it should be live. I've included a thank you note too in the article.
ve...@google.com <ve...@google.com>
ve...@google.com <ve...@google.com> #3
Great! Thanks a lot, I'll look for the live updates soon!
ma...@zentity.com <ma...@zentity.com> #4
Hi I guys, same problem in version 1.0.0-alpha03, but in my case the stacktrace is:
java.lang.NullPointerException: Attempt to invoke virtual method 'androidx.fragment.app.FragmentTransaction androidx.fragment.app.FragmentManager.beginTransaction()' on a null object reference
at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:219)
at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:191)
at androidx.biometric.FingerprintDialogFragment.handleDismissDialog(FingerprintDialogFragment.java:334)
at androidx.biometric.FingerprintDialogFragment$H.handleMessage(FingerprintDialogFragment.java:99)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6623)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
This happens after screen rotation when I touch with finger to sensor. No special rotation handling in my case, so the view is always recreated.
There is part of code which I use to show the BiometricPrompt dialog:
class SetFingerprintFragment : BaseFragment() {
private val viewModel by viewModel<SetFingerprintViewModel>()
...
private fun scanFingerprint(fingerprintCipher: Cipher) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Title TODO")
.setDescription("Desc TODO")
.setNegativeButtonText(getLocalizedString(R.string.general_cancel))
.build()
val biometricPrompt = BiometricPrompt(
requireActivity(),
MainThreadExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
viewModel.fingerprintConfirmed(requireNotNull(result.cryptoObject?.cipher))
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
return
}
Toast.makeText(context, errString, Toast.LENGTH_LONG).show()
}
}
)
// authenticate fingerprint and open cipher key for encryption
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(fingerprintCipher))
}
}
java.lang.NullPointerException: Attempt to invoke virtual method 'androidx.fragment.app.FragmentTransaction androidx.fragment.app.FragmentManager.beginTransaction()' on a null object reference
at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:219)
at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:191)
at androidx.biometric.FingerprintDialogFragment.handleDismissDialog(FingerprintDialogFragment.java:334)
at androidx.biometric.FingerprintDialogFragment$H.handleMessage(FingerprintDialogFragment.java:99)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6623)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
This happens after screen rotation when I touch with finger to sensor. No special rotation handling in my case, so the view is always recreated.
There is part of code which I use to show the BiometricPrompt dialog:
class SetFingerprintFragment : BaseFragment() {
private val viewModel by viewModel<SetFingerprintViewModel>()
...
private fun scanFingerprint(fingerprintCipher: Cipher) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Title TODO")
.setDescription("Desc TODO")
.setNegativeButtonText(getLocalizedString(R.string.general_cancel))
.build()
val biometricPrompt = BiometricPrompt(
requireActivity(),
MainThreadExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
viewModel.fingerprintConfirmed(requireNotNull(result.cryptoObject?.cipher))
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
return
}
Toast.makeText(context, errString, Toast.LENGTH_LONG).show()
}
}
)
// authenticate fingerprint and open cipher key for encryption
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(fingerprintCipher))
}
}
ma...@zentity.com <ma...@zentity.com> #5
Hi,
Figure out, where is problem. The key is the Biometric Prompt depends on lifecycle observer, which is attached to activity while creating BiometricPrompt instance. Thus it's crucial to create BiometricPrompt instance on every view recreation. In my case the correct implementation is:
class SetFingerprintFragment : BaseFragment() {
private val viewModel by viewModel<SetFingerprintViewModel>()
private lateinit var biometricPrompt: BiometricPrompt
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
biometricPrompt = BiometricPrompt(
requireActivity(),
MainThreadExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
viewModel.fingerprintConfirmed(requireNotNull(result.cryptoObject?.cipher))
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
return
}
Toast.makeText(context, errString, Toast.LENGTH_LONG).show()
}
}
)
}
private fun scanFingerprint(fingerprintCipher: Cipher) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Title TODO")
.setDescription("Desc TODO")
.setNegativeButtonText(getLocalizedString(R.string.general_cancel))
.build()
// authenticate fingerprint and open cipher key for encryption
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(fingerprintCipher))
}
}
Figure out, where is problem. The key is the Biometric Prompt depends on lifecycle observer, which is attached to activity while creating BiometricPrompt instance. Thus it's crucial to create BiometricPrompt instance on every view recreation. In my case the correct implementation is:
class SetFingerprintFragment : BaseFragment() {
private val viewModel by viewModel<SetFingerprintViewModel>()
private lateinit var biometricPrompt: BiometricPrompt
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
biometricPrompt = BiometricPrompt(
requireActivity(),
MainThreadExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
viewModel.fingerprintConfirmed(requireNotNull(result.cryptoObject?.cipher))
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
return
}
Toast.makeText(context, errString, Toast.LENGTH_LONG).show()
}
}
)
}
private fun scanFingerprint(fingerprintCipher: Cipher) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Title TODO")
.setDescription("Desc TODO")
.setNegativeButtonText(getLocalizedString(R.string.general_cancel))
.build()
// authenticate fingerprint and open cipher key for encryption
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(fingerprintCipher))
}
}
le...@gmail.com <le...@gmail.com> #6
Although recreating the prompt seems to prevent this issue, there is also another problem that occurs when you do so that I reported (https://issuetracker.google.com/issues/123811924 ).
So it seems that whatever approach you take, crashes might occur no matter what.
So it seems that whatever approach you take, crashes might occur no matter what.
kc...@google.com <kc...@google.com> #7
Over to Josh to take a look when he has time
jo...@google.com <jo...@google.com> #8
This bug should be fixed with the above cl and following the steps to recreate the prompt as in #5
Description
- Reproduced on a device with Android O
I'm providing the source code and an apk that reproduce the issue.
Steps to reproduce:
- Start my apk
- Rotate the screen to landscape when biometric dialog is visible (you can rotate an arbitrary number of times, it doesn't matter)
- Place the fingerprint
- Crash
Sourcecode:
Stacktrace:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.lithium.leona.openstud, PID: 6782
java.lang.IllegalStateException: Fragment FingerprintDialogFragment{fcd4576 (b3672114-cb16-4247-b15b-c5517c72cc9d)} not associated with a fragment manager.
at androidx.fragment.app.Fragment.requireFragmentManager(Fragment.java:824)
at androidx.fragment.app.DialogFragment.dismissInternal(DialogFragment.java:219)
at androidx.fragment.app.DialogFragment.dismiss(DialogFragment.java:191)
at androidx.biometric.FingerprintDialogFragment.handleDismissDialog(FingerprintDialogFragment.java:334)
at androidx.biometric.FingerprintDialogFragment$H.handleMessage(FingerprintDialogFragment.java:99)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)