Guide to Firebase Phone Authentication in Android Using Kotlin

Published Jan 30, 2018
Guide to Firebase Phone Authentication in Android Using Kotlin

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

private fun signIn(){
        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.

override fun onActivityResult(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 Authentication section. Under the Sign-In method tab, enable Phone authentication.
  • 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.

private fun startPhoneNumberVerification(phoneNumber: String) {
               PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber, // Phone number to verify
                60,             // Timeout duration
                TimeUnit.SECONDS,   // Unit of timeout
                this,           // Activity (for callback binding)
                mCallbacks)        // OnVerificationStateChangedCallbacks
    }

mCallbacks are callbacks for the result of verifying the phone number. Let's declare it:

    lateinit var mCallbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks

And override its methods:

 mCallbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
            override fun onVerificationCompleted(credential: PhoneAuthCredential) {
                // verification completed
                showSnackbar("onVerificationCompleted:" + credential)
                .
                .
            }

            override fun onVerificationFailed(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.")
                } else if (e is FirebaseTooManyRequestsException) {
                    // The SMS quota for the project has been exceeded
                    showSnackbar("Quota exceeded.")
                }
                
            }
            override fun onCodeSent(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

            }
            override fun onCodeAutoRetrievalTimeOut(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:

private fun 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.

override fun onCodeSent(verificationId: String?,
                                    token: PhoneAuthProvider.ForceResendingToken?)

And pass it to PhoneAuthProvider.getInstance().verifyPhoneNumber() method

private fun resendVerificationCode(phoneNumber: String,
                                       token: PhoneAuthProvider.ForceResendingToken?) {
        PhoneAuthProvider.getInstance().verifyPhoneNumber(
                phoneNumber, // Phone number to verify
                60, // Timeout duration
                TimeUnit.SECONDS, // Unit of timeout
                this, // 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 :

override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        outState!!.putBoolean(KEY_VERIFY_IN_PROGRESS, mVerificationInProgress)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        mVerificationInProgress = savedInstanceState.getBoolean(KEY_VERIFY_IN_PROGRESS)
    }

Demo

ezgif.com-gif-maker.gif

Discover and read more posts from Ememobong Akpanekpo
get started