Guide to Firebase Phone Authentication in Android Using Kotlin
Originally published on Jan 18, 2018 here
Firebase has done a lot of work in making phone number authentication less of a hassle for Android developers. I'll be showing you how easy it is to implement it in Android, using Kotlin. Please note that you will need a physical device to run phone authentication successfully.
There are two ways you can go about authenticating your user phone number and I'll take you through both of them.
Using Firebase UI
This is the easiest way you can go about implementing Firebase Phone Authentication. The FirebaseUI offers you a set of open source libraries that allows you to quickly connect with common UI elements and provides simple interfaces for common tasks, like displaying and authenticating users. Authentication methods supported for now include: Google, Facebook, Twitter, Email and Password, GitHub, and Phone Number.
Getting Started
Let's gather the things we will need to make things run smoothly.
First — make sure your app is connected to Firebase. See here for a quick guide from the Firebase Team on how to go about it. Go ahead and add Firebase Authentication and UI dependencies:
implementation 'com.google.firebase:firebase-auth:11.8.0' implementation 'com.firebaseui:firebase-ui-auth:3.1.3' Be sure to check what the current version of Firebase is if it gives you any errors.
Next — go to your Firebase console for the app. Go to the Authentication section. Under the Sign-In method tab, enable Phone authentication.
Create an activity with a button that will handle the sign-in logic. You can check out the XML file used for the app here.
Check if user is signed-in
It's always important you check for this before signing-in:
val isUserSignedIn = FirebaseAuth.getInstance().currentUser != null but_fui_sign_in_.setOnClickListener({ if (!isUserSignedIn) signIn() }) The signIn method
privatefunsignIn(){ val params = Bundle() params.putString(AuthUI.EXTRA_DEFAULT_COUNTRY_CODE, "ng") params.putString(AuthUI.EXTRA_DEFAULT_NATIONAL_NUMBER, "23456789") val phoneConfigWithDefaultNumber = AuthUI.IdpConfig.Builder(AuthUI.PHONE_VERIFICATION_PROVIDER) .setParams(params) .build() startActivityForResult( AuthUI.getInstance() .createSignInIntentBuilder() .setAvailableProviders( Arrays.asList(phoneConfigWithDefaultNumber)) .build(), RC_SIGN_IN) } Finally, check the result that is returned and proceed
In our onActivityResult function, we will check if our request was successful or if it failed. RC_SIGN_IN is the request code we passed into startActivityForResult() method above.
overridefunonActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) // RC_SIGN_IN is the request code you passed into // startActivityForResult(...)// when starting the sign in flow.if (requestCode == RC_SIGN_IN) { val response = IdpResponse.fromResultIntent(data) when { resultCode == Activity.RESULT_OK -> { // Successfully signed in showSnackbar("SignIn successful") return } response == null -> { // Sign in failed// User pressed back button showSnackbar("Sign in cancelled") return } response.errorCode == ErrorCodes.NO_NETWORK -> { // Sign in failed//No Internet Connection showSnackbar("No Internet connection") return } response.errorCode == ErrorCodes.UNKNOWN_ERROR -> { // Sign in failed//Unknown Error showSnackbar("Unknown error") return } else -> { showSnackbar("Unknown Response") } } } } Sign-out the user
Check if the user is signed-in before signing-out the user:
val isUserSignedIn = FirebaseAuth.getInstance().currentUser != null if (isUserSignedIn) signOut()}) fun signOut(){ AuthUI.getInstance() .signOut(this) .addOnCompleteListener { // user is now signed out showSnackbar("sign out successful") } } Using the Firebase SDK
There are times where we would love to give our user the look and feel of our beautiful login screen with all of the animations included. For this, we will need the Firebase SDK to implement phone number sign-in.
Before you dig in
You should make sure your app is connected to Firebase. If not, do so here.
Go to your Firebase console for the app. Go to the
Authenticationsection. Under theSign-In methodtab, enablePhoneauthentication.Add Firebase Authentication dependency:
implementation 'com.google.firebase:firebase-auth:11.8.0' You can create your activity and layout according to your look and feel or you can use this one.
The Phone Authentication Logic
User enters a phone number.
It internally uses the user's phone to determine its locale to properly identity the phone number. For most cases, you won't need to worry about country code, though you should consider this if going into production.
A unique code will be sent to the user's phone.
If the same phone is being used for the authentication, Firebase will automatically listen for SMS broadcasts on the device, retrieve the code, and verify the user. Interesting, huh!
If the user is on a different device, you will have to manually enter the verification code to verify the number.
Voilà, that's it.
Begin the verification process
To begin the verification process, retrieve the user phone number and verify using Firebase PhoneAuthProvider.verifyPhoneNumber method.
privatefunstartPhoneNumberVerification(phoneNumber: String) { PhoneAuthProvider.getInstance().verifyPhoneNumber( phoneNumber, // Phone number to verify 60, // Timeout duration TimeUnit.SECONDS, // Unit of timeoutthis, // Activity (for callback binding) mCallbacks) // OnVerificationStateChangedCallbacks } mCallbacks are callbacks for the result of verifying the phone number. Let's declare it:
lateinitvar mCallbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks And override its methods:
mCallbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { overridefunonVerificationCompleted(credential: PhoneAuthCredential) { // verification completed showSnackbar("onVerificationCompleted:" + credential) . . } overridefunonVerificationFailed(e: FirebaseException) { // This callback is invoked if an invalid request for verification is made,// for instance if the the phone number format is invalid. showSnackbar("onVerificationFailed") if (e is FirebaseAuthInvalidCredentialsException) { // Invalid request showSnackbar("Invalid phone number.") } elseif (e is FirebaseTooManyRequestsException) { // The SMS quota for the project has been exceeded showSnackbar("Quota exceeded.") } } overridefunonCodeSent(verificationId: String?, token: PhoneAuthProvider.ForceResendingToken?) { // The SMS verification code has been sent to the provided phone number, we// now need to ask the user to enter the code and then construct a credential// by combining the code with a verification ID. showSnackbar("onCodeSent:" + verificationId) // Save verification ID and resending token so we can use them later mVerificationId = verificationId mResendToken = token } overridefunonCodeAutoRetrievalTimeOut(verificationId: String?) { // called when the timeout duration has passed without triggering onVerificationCompleted super.onCodeAutoRetrievalTimeOut(verificationId) } } Get credentials
To sign-in a user, you must get the PhoneAuthCredential from onVerificationCompleted(credential: PhoneAuthCredential) or create one using the verificationId and code sent to the user:
val credential = PhoneAuthProvider.getCredential(verificationId, code) Sign-In the user
Now, you can sign-in the user with the credentials:
privatefun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { mAuth.signInWithCredential(credential) .addOnCompleteListener(this) { task -> if (task.isSuccessful) { // Sign in success, update UI with the signed-in user's information showSnackbar("signInWithCredential:success") } else { // Sign in failed, display a message and update the UI showSnackbar("signInWithCredential:failure") if (task.exception is FirebaseAuthInvalidCredentialsException) { // The verification code entered was invalid showSnackbar("Invalid code was entered") } // Sign in failed } } } User didn't receive verification code — Resend!
To resend the verification code, you must store the token passed into the onCodeSent() callback.
overridefunonCodeSent(verificationId: String?, token: PhoneAuthProvider.ForceResendingToken?) And pass it to PhoneAuthProvider.getInstance().verifyPhoneNumber() method
privatefunresendVerificationCode(phoneNumber: String, token: PhoneAuthProvider.ForceResendingToken?) { PhoneAuthProvider.getInstance().verifyPhoneNumber( phoneNumber, // Phone number to verify 60, // Timeout duration TimeUnit.SECONDS, // Unit of timeoutthis, // Activity (for callback binding) mCallbacks, // OnVerificationStateChangedCallbacks token) // ForceResendingToken from callbacks } Sign-out the user
Check if user is signed-in before signing-out the user:
val isUserSignedIn = FirebaseAuth.getInstance().currentUser != null if (isUserSignedIn) signOut()}) fun signOut(){ FirebaseAuth.getInstance().signOut() } Keeping state
It is important to keep the state of verification process to prevent unneccessary calls to the server. The simplest way to go about it will be using savedInstanceState :
overridefunonSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState!!.putBoolean(KEY_VERIFY_IN_PROGRESS, mVerificationInProgress) } overridefunonRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) mVerificationInProgress = savedInstanceState.getBoolean(KEY_VERIFY_IN_PROGRESS) } 