How I moved Firebase phone verification verifyPhoneNumber to a repository class. [Kotlin]

Merhawi Fissehaye
2 min readJun 9, 2020

Separation of concerns is one of the core principles in programming. When you separate different concerns into different classes and functions, your code becomes more manageable, and you move away from writing a brittle code to writing a robust and testable code.

When I encountered the verifyPhoneNumber function in the documentation, my first concern was where do I place this code. Now here is why I asked that question. But first take a look at this piece of code.

PhoneAuthProvider.getInstance().verifyPhoneNumber(
phoneNumber, // Phone number to verify
60, // Timeout duration
TimeUnit.SECONDS, // Unit of timeout
this, // Activity (for callback binding)
mCallbacks); // OnVerificationStateChangedCallbacks

As you can see, the code block requires an activity. Therefore you have to either make this call from within your activity or you have to pass the launcher activity to whoever is making the call. But I knew somehow I have to figure out a way to make this call from a repository class that has no concern for the activity. Separation of concern is not the only reason, but a more important reason is to make sure that your call is lifecycle aware and doesn’t cause memory leaks or null pointer exceptions accidentally on a fragment or activity change.

When you take a look at the prototype of the function, you will quickly learn that you can also pass an executor instead of an activity which is a good news because you have access to an executor inside a ViewModel.

So I first created an interface to notify my fragment of important events like so:

sealed class OTPStatus {
object OTPLoading : OTPStatus()
object OTPSent : OTPStatus()
object OTPVerified : OTPStatus()
data class OTPFailed(val error: Exception): OTPStatus()
}

And then I wrote the following code inside the ViewModel

val liveData = MutableLiveData<OTPStatus>()
liveData.value = OTPStatus.OTPLoading()
repository.sendOTP(
phoneForm.getFormattedPhoneNumber(),
Dispatchers.IO.asExecutor(),
object : OTPListener {
override fun sent(id: String, token: PhoneAuthProvider.ForceResendingToken) {
storedVerificationId = id // store state inside view model
resendToken = token
liveData.postValue(OTPSuccess.OTPSent)
}

override fun verified(credential: PhoneAuthCredential) {
liveData.postValue(OTPStatus.OTPSuccess.OTPVerified)
}

override fun error(ex: Exception) {
liveData.postValue(OTPStatus.OTPFailed(ex))
}
}, viewModelScope
)

Then the repository class will make the main network call. Note the call for Dispatchers.IO.asExecutor() and passing viewModelScope.

fun sendOTP(phone: String, executor: Executor, listener: OTPListener, scope: CoroutineScope) =
PhoneAuthProvider.getInstance().verifyPhoneNumber(
phone,
60,
TimeUnit.SECONDS,
executor,
object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
try {
auth.currentUser?.linkWithCredential(credential) ?: run {
scope.launch {
signInWithCredential(credential)
}
}
listener.verified((credential))
} catch (ex: Exception) {
listener.error(ex)
}
}

override fun onVerificationFailed(ex: FirebaseException) {
if (ex is FirebaseAuthInvalidCredentialsException) listener.error(
InvalidCredentialsException()
)
if (ex is FirebaseTooManyRequestsException) listener.error(
TooManyRequestException()
)
else listener.error(ex)
}

override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken
) {
listener.sent(verificationId, token)
}
}
)

The listener interface is a simple interface to make the communication between the viewmodel and the repository easier.

I know it is not a perfect solution, because I am still keeping reference to Firebase specific classes inside my ViewModel such as PhoneAuthProvider.ForceResendingToken. I would love to hear if anyone figured a better way to totally encapsulate all the firebase calls to a single class.

Cheers

--

--

Merhawi Fissehaye

አሳቢና ጸሓፊ ነኝ። በተለይም ኃሳብ ከሰዎች ጋር ባለው ትስስርና እንዴት ከእውነታ ጋር መገናኘት እንደሚችል መመራመር እወዳለሁ። በጽሑፎቼም እነዚህን ግኝቶቼን አሰፍራለሁ።