How to return a list from Firestore database as a result of a function in Kotlin?

I'm building an app for a friend and I use Firestore. What I want is to display a list of favorite places but for some reason, the list is always empty.

I cannot get the data from Firestore. This is my code:

fun getListOfPlaces() : List<String> {
    val places = ArrayList<String>()
    placesRef.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            for (document in task.result) {
                val name = document.data["name"].toString()
                places.add(name)
            }
        }
    }
    return list;
}

If I try to print, let's say the size of the list in onCreate function, the size is always 0.

Log.d("TAG", getListOfPlaces().size().toString()); // Is 0 !!!

I can confirm Firebase is successfully installed. What am I missing?

This is a classic issue with asynchronous web APIs. You cannot return something now, that hasn't been loaded yet. With other words, you cannot simply return the places list as a result of a method because it will always be empty due the asynchronous behavior of the onComplete function. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.

But not only Cloud Firestore loads data asynchronously, almost all of modern other web APIs do, since it may take some time to get the data. But let's take an quick example, by placing a few log statements in the code, to see more clearly what I'm talking about.

fun getListOfPlaces() : List<String> {
    Log.d("TAG", "Before attaching the listener!");
    val places = ArrayList<String>()
    placesRef.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            Log.d("TAG", "Inside onComplete function!");
            for (document in task.result) {
                val name = document.data["name"].toString()
                places.add(name)
            }
        }
    }
    Log.d("TAG", "After attaching the listener!");
    return list;
}

If we run this code will, the output in your logcat will be:

Before attaching the listener!

After attaching the listener!

Inside onComplete function!

This is probably not what you expected, but it explains precisely why your places list is empty when returning it.

The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against it. Here is an excelent article written by Doug Stevenson that I'll highly recommend you to read.

A quick solve for this problem would be to use the places list only inside the onComplete function:

fun readData() {
    placesRef.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val list = ArrayList<String>()
            for (document in task.result) {
                val name = document.data["name"].toString()
                list.add(name)
            }
            //Do what you need to do with your list
        }
    }
}

If you want to use the list outside, there is another approach. You need to create your own callback to wait for Firestore to return you the data. To achieve this, first you need to create an interface like this:

interface MyCallback {
    fun onCallback(value: List<String>)
}

Then you need to create a function that is actually getting the data from the database. This method should look like this:

fun readData(myCallback : MyCallback) {
    placesRef.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val list = ArrayList<String>()
            for (document in task.result) {
                val name = document.data["name"].toString()
                list.add(name)
            }
            myCallback.onCallback(list)
        }
    }
}

See, we don't have any return type anymore. In the end just simply call readData() function in your onCreate function and pass an instance of the MyCallback interface as an argument like this:

readData(object: MyCallback {
    override fun onCallback(value: List<String>) {
        Log.d("TAG", list.size.toString())
    }
})

If you are using Kotlin, please check the other answer.

Realtime Database vs. Cloud Firestore, What is the difference between cloud firestore and realtime database? A short simple guide to using Firebase’s RealTime NoSQL database from your Android app using Kotlin. The Firebase Database Api focuses on the powerful realtime features of NoSQL.

The reason for having a empty list got perfectly answered by Alex Mamo above.

I just like to present the same thing without needing to add an extra interface.

In Kotlin you could just implement it like so:

fun readData(myCallback: (List<String>) -> Unit) {
    placesRef.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val list = ArrayList<String>()
            for (document in task.result) {
                val name = document.data["name"].toString()
                list.add(name)
            }
            myCallback(list)
        }
    }
}

and then use it like so:

readData() {
   Log.d("TAG", it.size.toString())
})

Get data with Cloud Firestore, stores data in documents arranged in collections. Simple data is stored in documents, which is easy and similar to the way data is stored in JSON. By default, queries retrieve results from a single collection in your database. Use a collection group query to retrieve results from a collection group instead of from a single collection. List subcollections of a document. The getCollections() method of the Cloud Firestore server client libraries lists all subcollections of a document reference.

Nowadays, Kotlin provides a simpler way to achieve the same result as in the case of using a callback. This answer is going to explain how to use Kotlin's coroutines. In order to make it work, we need to add the following dependency in our build.gradle file:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.2.1"

This library that we use is called Module kotlinx-coroutines-play-services and is used for the exact same purpose. As we already know, there is no way we can return a list of objects as a result of a method because get() returns immediately but the callback from the Task it returns will be called sometime later. That's the reason we should wait until the data is available.

On the Task object that is returned when calling get(), we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin's coroutines. For that, we need to create a suspend function that looks like this:

private suspend fun getListOfPlaces(): List<DocumentSnapshot> {
    val snapshot = placesRef.get().await()
    return snapshot.documents
}

As you can see, we have now an extension function called await() that will interrupt the coroutine until the data from the database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:

private suspend fun getDataFromFirestore() {
    try {
        val listOfPlaces = getListOfPlaces()
    }
    catch (e: Exception) {
        Log.d(TAG, e.getMessage()) //Don't ignore errors!
    }
}

Work with Lists of Data on Android, How to return a list from Firestore database as a result of a function in Kotlin? - firebase. In this article, we will learn about how to use return in kotlin programming language. For example, returning from function or anonymous function or inline function, returning from a lambda expression etc. Please note that we would be talking about 2 types of return in kotlin in this article – 1. Unlabeled return in kotlin 2. Labeled return

Querying and filtering data | Firestore, Either of these methods can be used with documents, collections of documents, or the results of queries: Call a method to get the data. Set a listener to receive  Cloud Firestore provides powerful query functionality for specifying which documents you want to retrieve from a collection or collection group. These queries can also be used with either get () or addSnapshotListener (), as described in Get Data and Get Realtime Updates. Note: While the code samples cover multiple languages, the text

Getting realtime updates | Firestore, Cloud Firestore triggers · Realtime Database triggers · Remote Config This document covers working with lists of data in Firebase. Java Kotlin+KTX More You can use the reference to the new data returned by the push() method to The call to the orderByChild() method specifies the child key to order the results by. Kotlin Firestore example – CRUD Operations with RecyclerView | Android Cloud Firestore helps us store data in the cloud. It supports offline mode so our app will work fine (write, read, listen to, and query data) whether device has internet connection or not, it automatically fetches changes from our database to Firebase Server.

Tutorial: Cloud Firestore, The following query returns all cities with state CA : Web Swift More After creating a query object, use the get() function to retrieve the results: Web Swift More. Cloud Firestore Queries using Kotlin. In this video we're gonna see how to use Firebase Cloud Firestore's querying capability. We're gonna see about the implementation of Cloud Firestore's Simple

Comments
  • Simply check this out.
  • Thanks but it is for Java, I don't know Java, I learn Kotlin. Can you solve this in Kotlin?
  • Ok, I'll write you an answer in Kotlin.
  • Ok, keep me posted.
  • That's fabulous, it worked. Thanks for your great explanation!!
  • I have also seen the video from that post, it's almost the same in Java.
  • Thanks @AlexMamo for your great answers and explanations, It always helps a lot. Which one is the best way to get the result from the asynchronous APIs by using the callback or using the runBlocking thread and then returning the result using async function.
  • @deepakkumar Using the callback.
  • Very lean and clean solution. Works like a charm.