DEV Community

Rosário Pereira Fernandes
Rosário Pereira Fernandes

Posted on • Edited on

Using Kotlin Extension Functions and Coroutines with Firebase

Portuguese Version available on developingwith.firebaseapp.com

"The simpler the code, the faster you'll understand it." -
Paulo Enoque

Kotlin's simplicity and concision are the main reason why developers have been adopting this language for their work.
I must also remind you that, in May 2017, Google announced that Kotlin became an official language for Android App Development. Ever since then, the number of Kotlin developers has been increasing.


A year and a half after Kotlin became the official language for Android Development, Firebase added this language to their official documentation :

But I must agree with the popular saying "Better late than never". This change brought many improvements to the platform:

  • The Java code on the Firebase Android SDK was improved for better interoperability with Kotlin;
  • "Extension Functions" were created to make the use of the Android SDK more concise;
  • Some developers created libraries to improve the way Firebase is used with Kotlin, etc.

I decided to create a library to help people use Firebase in Kotlin, it was called fireXtensions (now deprecated) and it used to provide some Extension Functions for the Firebase Android SDK.

Kotlin Extension Functions

Putting it short, Extension Functions are a Kotlin feature that allows you to add new methods/functions to a class, even if this class was not created by you. No need to implement the class nor extend it.

After adding Kotlin to their Official Documentation, the Firebase Team has also added a few Extension Functions to their Android SDK (thus deprecating fireXtensions).

The first extensions were the libraries Common KTX and Firestore KTX.

Common KTX

The firebase-common-ktx module contains extension functions used to obtain the Firebase Instance. If you've ever worked with Firebase (either in Java or Kotlin), you're probably familiarized with the method/function FirebaseApp.getInstance(). If you use the firebase-common-ktx module, calling this method gets easier: Firebase.app.

In order to use this module, make sure you have the Kotlin Plugin 1.3.20 or higher on your build.gradle(project) file:

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
}

And then add the following dependency to your build.gradle(app):

dependencies {
    // ... Other dependencies ...
    implementation 'com.google.firebase:firebase-common-ktx:16.1.0'
}

See the full list of available extension functions.

Firestore KTX

As the name suggests, this module contains extensions that simplify the way you use the Firestore Android SDK. This module can be added to your project the same way we did with Common KTX, but using this dependency instead:

dependencies {
    // ... Other dependencies ...
    implementation 'com.google.firebase:firebase-firestore-ktx:18.2.0'
}

See the full list of available extension functions.

Note that when using the firebase-firestore-ktx dependency, you no longer need to use com.google.firebase:firebase-firestore.

Although these are currently the only available modules, I'm pretty sure more modules will be added to their SDK. I'll update this post every time a new module is added.

Kotlin Coroutines

As you might already know, Firebase APIs are asynchronous, forcing us to use listeners in our code to read data from our database. And programmers often try to (wrongly) read data without listeners, or try to use the data outside those listeners, like this:

Example 1: Read all users stored in Firestore.

// What we would like to do
// (PS: This code doesn't work)
val db = FirebaseFirestore.getInstance()
var users = db.collection("users").get()
updateUI(users)

// (This code doesn't work either)
// What some people do (wrongly)
var users: List<User>()? = null
usersRef.get().addOnSuccessListener { querySnapshot ->
    users = querySnapshot.toObjects(User::class.java)
}
if (users == null) {
    displayError()
    // displayError() will always be called because
    // the users list is loaded asynchronously.
    // The if is executed while the list hasn't been loaded yet.
} else {
    updateUI(users)
}



// What they should do:
usersRef.get()
    .addOnSuccessListener { querySnapshot ->
        val users = querySnapshot.toObjects(Userr::class.java)
        updateUI(users)
        // The method is now being called after
        // loading the users list.
    }.addOnFailureListener { e ->
        displayError()
    }

Looking at the code above, you might think there's nothing wrong with listeners, because everything makes sense and even keeps the code organized. But what if you need to read data from different collections and merge the results before displaying on the app UI? This would force us to use nested listeners, which is not so easy to read, as we can see bellow:

Example 2: Load John's profile and his list of friends (this list is stored under a different collection).

// Allow me to send a shot out to all
// JavaScript Developers who have been to callback hell
usersRef.document("john").get().addOnSuccessListener { querySnapshot ->
            val johnUser = querySnapshot.toObject(User::class.java)

            friendsRef.get().addOnSuccessListener { friendSnapshot ->
                val friends = friendSnapshot.toObjects(Friend::class.java)
                showProfileAndFriends(johnUser, friends)
            }.addOnFailureListener {
                displayError()
            }

        }.addOnFailureListener { e ->
            displayError()
        }

Coroutines have come to change that. They allow you to write asynchronous code as if it was synchronous, making it more concise and easier to read.
In order to use Coroutines in your Android Project, make sure you're using version 1.3.x or higher of the Kotlin Plugin, and then add the following dependencies to your build.gradle(app) file:

dependencies {
    // ... Other Dependencies ...
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
}

As of the time this was posted, 1.1.1 was the latest version. You can check the maven repository for the latest version of kotlinx-coroutines-play-services.

Now, if we used Coroutines in our first example, it would become:

try {
    val snapshot = usersRef.get().await()
    val users = snapshot.toObjects(User::class.java)
    updateUI(users)
} catch (e: FirebaseFirestoreException) {
    displayError()
}

It resembles our "What we would like to do" code from example 1, doesn't it?

Now our second example:

try {
    val querySnapshot = usersRef.document("john").get().await()
    val johnUser = querySnapshot.toObject(User::class.java)

    val friendSnapshot = friendsRef.get().await()
    val friends = friendSnapshot.toObjects(Friend::class.java)
    showProfileAndFriends(johnUser, friends)
} catch (e: FirebaseFirestoreException) {
    displayError()
}

The code now looks synchronous and easier to read, right?


And that's all. I hope you found these Kotlin features useful and I hope that they'll improve your productivity when developing firebase apps.

If you have any doubts or suggestions, please leave it on the comments bellow.

If you're trying to use the Kotlin Extensions or Coroutines and ran into a problem, you can post it on StackOverflow explaining what you did and what error you ran into. I'm sure you'll find help (either from me or someone from the Firebase Community).

Top comments (4)

Collapse
 
gastonsaillen profile image
Gaston Saillen • Edited

Just make sure if you are using .await() into any Firebase call , that you call this inside a suspend function, since .await() needs to be called inside one.

For realtime updates we can use Flow -> youtu.be/B8ppnjGPAGE?t=538

Collapse
 
adamshurwitz profile image
AdamHurwitz

It might be useful to update the post with a link to the most recent version for the kotlinx-coroutines-play-services library as AndroidStudio will not automatically highlight version updates.

See: mvnrepository.com/artifact/org.jet...

Collapse
 
rosariopfernandes profile image
Rosário Pereira Fernandes

Great suggestion! I've just updated it. Thanks! :)

Collapse
 
adamshurwitz profile image
AdamHurwitz • Edited

This is extremely useful for one-time requests such as Callbacks like 'addOnCompleteListener'.
For realtime updates I looked further into the 'suspendCancellableCoroutine' to build a simple pattern for realtime updates like 'addSnapshotListener'. I outlined the pattern in this StackOverflow post.

GitHub Post - stackoverflow.com/a/58786712/2253682