Oh FingerprintManager is deprecated? It’s replaced with this new thing BiometricPrompt that comes with it’s own UI and supports a wider range of biometrics? There’s even an androidx library that does the compatibility work for you? This will be easy! HAHAHAHHAHHAHA
This is the story of all the pain I’ve run into with implementing BiometricPrompt so you don’t have to.
If you just want a recommendation for today, it would be: Use FingerprintManager on Android 9, use BiometricPrompt on Android 10, throw in a few hacks for a better user experience, and cross your fingers and hope for the best.
It all started back in 2018, fresh off the google I/O hype train. I decided to look into this new BiometricPrompt thing. It looked easy to implement and came with an androidx lib in alpha that seemed to work alright. So I went ahead and implemented a rough POC in my project, demoed to our product owner on a Pixel 1 and everything looked great. “That was easy”, I thought. Little did I know what was to come.
Done with the happy path, I went on to implement the error handling. Making sure all was well, I made sure to test on both the BiometricPrompt and FingerprintManager paths. Unfortunately, I noticed some major differences on how things were handled such as cases where the user was locked out because of too many fingerprint attempts. Before Android 9 it would show the dialog with the error, but on Android 9 it would show nothing. “No worries”, I thought. The lib was in alpha. I filled a bug and put in a work-around.
With BiometricPrompt I also noticed that there could be several seconds between
when asking to show the dialog and when it actually showed or an error was
returned. I worked around this by adding in a loading indicator in the ui. But
when to stop showing it? There’s no callback when the BiometricPrompt is
actually shown and no lifecycle events are fired. Eventually, I found out that
the window loses focus when the prompt is shown. So with one
OnWindowFocusChangeListener later I got that fixed.
Given that biometrics is an optional feature for our users, naturally we’d want
a way to detect that they have it enabled. Well apparently nobody at Google
thought of that as they went ahead and deprecated
FingerprintManager.hasEnrolledFingerprints() without providing a replacement.
But no phone out there actually uses any other authentication besides
fingerprint right? So went ahead using that deprecated method (stay tuned).
Everything seemed fine until I got a bug from QA. Sometimes the prompt wasn’t
showing, only on Android 9 of course. I was confused, we show it in
no complicated logic there. Well it turns out that’s not good enough for
BiometricPrompt. From what I can only assume was to prevent from showing when
the app is in the background, if your window doesn’t have focus, the call will
simply be ignored. Furthermore, in the
onResume() callback, your window will
only sometimes have focus because having a consistent callback ordering in
android would be boring. Solution: another OnWindowFocusChangeListener then.
The implementation wasn’t as pretty now, but hey nothing that couldn’t be worked-around. Everything looked good and we shipped with our shiny new biometric prompt.
Not too long after we shipped, Samsung finally got around to updating their phones to Android 9. Now if you weren’t aware (I wasn’t) these phones had this nifty ‘feature’ where you could unlock your phone with your face or iris. Now you may be thinking, “hey this doesn’t sound all that secure.” Well guess what? It isn’t! That would be ok if it was only contained as an OS feature and didn’t affect app api’s, but this is Samsung we’re talking about. Mucking with api’s is their expertise. In their infinite wisdom they decided to update the stock biometric prompt work with face/iris. So now we have two wonderful issues:
FingerprintManager.hasEnrolledFingerprints()might not be right anymore. If the user has face/iris enrolled but no fingerprints, it’ll return false. If they have both it’ll return true, but show the face/iris unlock for the dialog. And of course we don’t have any other api to use instead.
Well the bad user reviews were coming in and we needed to figure out how to fix this quick. There’s no easy workaround we can add this time. So we did the next best thing: copy all of androidx biometric into our app and hard-code it to use the stock FingerprintManager implementation. And that’s where things stand today.
So here we are, using a compat lib but not really. We could’ve stuck with FingerprintManager and ignored all of this mess. However, Android 10 did just came out, and there’s been some improvements in the androidx lib, maybe there’s hope?
For one Android 10 did finally add a replacement to
FingerprintManager.hasEnrolledFingerprints() in the form of
BiometricManager.canAuthenticate(). Unfortunately that won’t help us on Android 9.
They also tightened the CTS (compatibly test suite) around this so with any
luck the Samsung issues will get fixed when they update to 10. Finally, they
seem to be receptive to add
some sort of work-around for the Samsung issue in the androidx lib. And with
the Pixel 4 around the corner which is rumored on only have face unlock, this
is good. As it stands today, we aren’t quite at the place we can drop something
in and call it a day, but we are a bit closer.