Skip to content

Using Coroutines with Firebase on Android

It’s no doubt that coroutines are taking over as the go-to way to do asynchronous programming in Android over RxJava.

Many of Firebase’s services use Task objects to perform asynchronous operations of their own, including Firestore, Authentication, Storage, and Remote Config.

The standard way proceed was to attach onComplete or onSuccess listeners to these Tasks, and handle data in those callbacks.

fun signIn(email: String, password: String) {
    auth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener {
            // Handle success and failure
        }
}

Although lambdas made it better, having callbacks everywhere was never the cleanest way to go about coding. It also makes separation of concerns slightly difficult.

Thus, the need to more loosely couple these methods brought different RxJava integrations with Firebas, such as this library by FrangSierra.

fun signIn(email: String, password: String) {
    RxFirebaseAuth.signInWithEmailAndPassword(auth, email, password)
        .map({ authResult -> authResult.getUser() != null })
        .take(1)
        .subscribe({ logged -> Log.i("Rxfirebase2", "User logged $logged") })
}

That was good for a while, but it came with one slight issue. You would either have to rely on libraries such as these, or create your own RxJava wrappers around these tasks which meant a lot of boilerplate.

Also, RxJava’s reign in the Android ecosystem is weakening every month, every day, even every year.

What if there was a better solution that didn’t rely on the heavy library that is RxJava (and potentially other libraries too)? And what if that solution became native to the development ecosystem?

Firebase with Coroutines

Coroutines is already the way forward with Android, and that can be said for Firebase as well. All of those functions above that use callbacks and observables to do a simple sign in? Well that becomes this.

suspend fun signIn(email: String, password: String): AuthResult {
    return auth.signInWithEmailAndPassword(email, password).await()
}

One-liner function. This is possible because of the await() extension function to tasks, provided to us by this Kotlin library.

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

Though this may not seem like a drastic change, consider that this is a very simple case. What if we had to chain more than operation together?

Let’s take Firebase Remote Config for example. With the Rx way, we’d have to flatMap quite a number of Maybe’s together to set config settings and fetch config parameters.

fun fetchAndActivateConfigParameters(): Completable {
    val config = FirebaseRemoteConfig.getInstance()
    val configSettings = FirebaseRemoteConfigSettings.Builder()
        .setFetchTimeoutInSeconds(1000)
        .build()

    return Completable.create { emitter ->
        RxCompletableHandler.assignOnTask(emitter, config.setConfigSettingsAsync(configSettings))
    }
        .andThen {
            Completable.create { emitter ->
                RxCompletableHandler.assignOnTask(emitter, config.setDefaultsAsync(R.xml.config_defaults))
            }
        }
        .doFinally { 
            RxFirebaseRemote.fetch(config)
        }
}

Even with access to special RxHandlers from the library above, I still end up with a function that seems all over the place. Don’t even get me started on how this would look like with onComplete callbacks.

Now let’s see how the same function looks like with Coroutines.

suspend fun fetchAndActivateConfigParameters() {
    val config = FirebaseRemoteConfig.getInstance()

    val configSettings  = FirebaseRemoteConfigSettings.Builder()
        .setMinimumFetchIntervalInSeconds(10)
        .build()
    config.setConfigSettingsAsync(configSettings).await()

    config.setDefaultsAsync(R.xml.remote_config_defaults).await()
    config.fetchAndActivate().await()
    
    applyConfigParameters(config)
}

Already the code is a million times easier to read. Everything is happening in an easy-to-understand order. No need to create hellish chains of observable streams because we can do everything quite sequentially using standard Coroutine logic.

Just for one more example, let me show you what a suspend function with Firestore could look like.

suspend fun addUserToRoom(roomId: String): DocumentReference {

    return firestore.collection("rooms")
        .document(roomId)
        .collection("users")
        .add(mapOf(
            Pair("id", user()!!.uid),
            Pair("name", user()!!.displayName),
            Pair("image", user()!!.photoUrl)
        ))
        .await()
}

Now you can hook this up to some LiveData, or simply use launch in a fire-and-forget fashion.

val errorHandler = CoroutineExceptionHandler { _, exception ->

    exception.printStackTrace()
    viewState.errorMessage = "Something went wrong. Please try again"
    viewState.enterBtnEnabled = true
    stateLiveData.postValue(viewState)
}

networkScope.launch(errorHandler) {

    firebaseRepository.addUserToRoom(roomId)
    launchRoomActivity(roomId)
}

Is there any reason to not use Coroutines with Firebase?

No need for any special libraries. The one library you do need is one that’s as close to native to Kotlin as an external library could be, and it’s fit for not just Firebase, but anything that makes use of a Task object.

Many of the code examples here were fetched from or based around my Firestore Chat MVVM app which you can check out on Github. Also do check out my post on how that app was made.