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 theSign-In method
tab, enablePhone
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)
}
Hi Ememobong,
Firebase UI Auth in 4.0.1 changed and AuthUI.somevalues or response.errorCodes are not available and the code is not working.
Do you know how to fix it?