Using Firebase on Android with Kotlin Coroutines

Whilst recently working on a side project I ran into a situation where I needed to make use of the Android Firebase SDKs. This project of mine uses Kotlin Coroutines for all of the asynchronous task handling, so ideally I wanted to keep any Firebase calls to use the same approach – not only so that the callbacks would be both consistant but also so that they would tie in with the current flows of data.

When originally faced with this work I had two initial thoughts:

  • Do I need to just wrap the Firebase calls myself inside of a wrapper class and then make calls to these where Firebase is needed?
  • Maybe someone has already made a third-party wrapper class that I can make use of, to save me having to do all of that groundwork and avoid reinventing the wheel

Before starting work on the above two points, I did some quick searching to see what other people were doing (which were pretty much the two points above!). However, at the same time I also came across the kotlinx-coroutines-play-services library – this provides extension functions for the Google Play Services Tasks API, allowing us to integrate Firebase calls into our coroutines setup! We can add this to our project using the following dependency:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'

When it comes to converting Firebase calls to a suspending function, the only extra work we need to do is utilise the await() extension function provided by the library. For an example of what this might look like when using a Firebase SDK, let’s take a look at an example using Firebase Auth.

firebaseAuth.signInWithEmailAndPassword(email, password).await()

Like most asynchronous operations from within Firebase SDKs, this authorisation method returns an instance of a Task. For this task we can add listeners that allow us to hook into change events such as when the task completes, is successful or fails. Whilst this callback style is completely usable, it doesn’t adhere too well to our use of coroutines. We could wrap this call (and each other call to the Firebase auth SDK) to fit in with our stack, but this should be a last resort to avoid boilerplate. Now that we have access to this coroutines-play-services library, we can wrap our signInWithEmailAndPassword and await for a result within a suspending function, which could look something like so:

class AuthenticationDataRepository constructor(
    private val firebaseAuth: FirebaseAuth
) {

    suspend fun authenticate(
        email: String, 
        password: String
    ): FirebaseUser? {
        firebaseAuth.signInWithEmailAndPassword(
            email, password).await()
        return firebaseAuth.currentUser ?: 
            throw FirebaseAuthException("", "")
    }
}

With the above code:

  • First, signInWithEmailAndPassword will trigger the authentication flow for firebase auth
  • With the use of the await() extension function (we’ll look at that next), no more code will be executed until this operation has completed
  • Finally, we’ll return the current user from our firebase auth reference or throw a FirebaseAuthException if this is null

One thing to note here is that the call to signInWithEmailAndPassword does not return a value, it simply sets the authenticated user within our Firebase Auth reference which we can then retrieve manually. If at this point you were expecting a value to be returned from await() then you would assign the returned value to a field (val / var).

Now, let’s shift our focus onto this await() extension function – this is not something that is available out of the box with Firebase, it is instead provided by kotlin coroutines itself. If we dive into the coroutines library containing Firebase support, we can see how this extension function is working under the hood:

public suspend fun <T> Task<T>.await(): T {
    // fast path
    if (isComplete) {
        val e = exception
        return if (e == null) {
            if (isCanceled) {
                throw CancellationException(
                    "Task $this was cancelled normally.")
            } else {
                result
            }
        } else {
            throw e
        }
    }

    return suspendCancellableCoroutine { cont ->
        addOnCompleteListener {
            val e = exception
            if (e == null) {
                if (isCanceled) cont.cancel() else cont.resume(result)
            } else {
                cont.resumeWithException(e)
            }
        }
    }
}

Within the first check for isComplete we can see that this is used where the given Task reference may already have completed at the call point. Here we begin by checking if there is an exception in the Task, if so then this exception is thrown. Otherwise, we next check if the Task has been cancelled, again throwing an exception if so. Finally, if neither of the above have been satisfied then we return the completed result.

On the other hand if the Task has not yet completed, we resume and return a suspendCancellableCoroutine reference (this is essentially a suspending coroutine providing a way to cancel the coroutine). Within this you may notice the addOnCompleteListener call – this is still using the same callback that we would have done inside of our own code for the call to signInWithEmailAndPassword, which would have looked something like so:

firebaseAuth.signInWithEmailAndPassword(email, password)
    .addOnCompleteListener {
     // handle result using callback
}

The nice thing with this extension function is that we don’t have to worry about handling that callback – because this signInWithEmailAndPassword call is inside of a data repository class, we’d likely have to pass a callback to our authenticate function to be used inside of the completion listener.

With this await() call being an extension function, the completion listener is being added to our task and then resume is used on the CancellableContinuation (cont) reference to return the result of our Task to the place which is awaiting for it. If the isCanceled flag is caught inside of the this completion listener then the continuation reference will be cancelled, the same for if an exception is raised at this point.


With the above in place, we can now place our firebase authentication call within our viewmodel class, utilising the viewModelScope to launch our coroutine:

viewModelScope.launch {
    try {
        repository.authenticate(email, password)?.let {
            authenticationState.postValue(
                AuthenticationState.Authenticated)
        } ?: run {
            authenticationState.postValue(
                AuthenticationState.Failed())
        }
    } catch (e: FirebaseAuthException) {
        authenticationState.postValue(
            AuthenticationState.Failed())
    }
}

This allows us to interact with the Firebase SDK in a more concise way, fitting it to the same way that the rest of our application is operating – which can be useful for multiple data streams and threading.

If you haven’t yet adopted coroutines, this could be a nice way to experiment with the functionality, allowing you to get used to how they behave and operate within the context of android applications. If you’re using this library already, or looking into playing with it, I’d love to hear any thoughts or questions that you may have on how it works!

Leave a Reply

Your email address will not be published. Required fields are marked *