¿Cómo devolver una lista de la base de datos de Firestore como resultado de una función en Kotlin?

Resuelto Jane Ashley asked hace 54 años • 3 respuestas

Estoy creando una aplicación para un amigo y uso Firestore. Lo que quiero es mostrar una lista de lugares favoritos pero por alguna razón la lista siempre está vacía.

ingrese la descripción de la imagen aquí

No puedo obtener los datos de Firestore. Este es mi código:

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;
}

Si intento imprimir, digamos el tamaño de la lista en onCreatefunción, el tamaño siempre es 0.

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

Puedo confirmar que Firebase se instaló correctamente. ¿Qué me estoy perdiendo?

Jane Ashley avatar Jan 01 '70 08:01 Jane Ashley
Aceptado

Este es un problema clásico con las API web asincrónicas. No puedes devolver algo ahora que aún no se ha cargado. En otras palabras, no puedes simplemente devolver la placeslista como resultado de un método porque siempre estará vacía debido al comportamiento asincrónico de la onCompletefunción. Dependiendo de la velocidad de su conexión y del estado, pueden pasar desde unos cientos de milisegundos hasta unos segundos antes de que los datos estén disponibles.

Pero no solo Cloud Firestore carga datos de forma asincrónica, casi todas las demás API web modernas lo hacen, ya que puede llevar algún tiempo obtener los datos. Pero tomemos un ejemplo rápido, colocando algunas declaraciones de registro en el código, para ver más claramente de qué estoy hablando.

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;
}

Si ejecutamos este código, la salida en su logcat será:

¡Antes de adjuntar al oyente!

¡Después de adjuntar al oyente!

¡Dentro de la función onComplete!

Probablemente esto no sea lo que esperabas, pero explica precisamente por qué tu lista de lugares está vacía cuando la devuelves.

La respuesta inicial de la mayoría de los desarrolladores es intentar "arreglar" este problema asynchronous behavior, lo cual personalmente recomiendo. Aquí hay un excelente artículo escrito por Doug Stevenson que le recomiendo encarecidamente que lea.

Una solución rápida para este problema sería utilizar la lista de lugares sólo dentro de la onCompletefunción:

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
        }
    }
}

Si desea utilizar la lista en el exterior, existe otro método. Debes crear tu propia devolución de llamada para esperar a que Firestore te devuelva los datos. Para lograr esto, primero necesitas crear algo interfacecomo este:

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

Luego necesita crear una función que realmente obtenga los datos de la base de datos. Este método debería verse así:

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)
        }
    }
}

Mira, ya no tenemos ningún tipo de devolución. Al final, simplemente llame a readData()la función en su onCreatefunción y pase una instancia de la MyCallbackinterfaz como un argumento como este:

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

Si está utilizando Kotlin, consulte la otra respuesta .

Alex Mamo avatar Jul 30 '2018 13:07 Alex Mamo

Hoy en día, Kotlin proporciona una forma más sencilla de lograr el mismo resultado que en el caso de utilizar una devolución de llamada. Esta respuesta explicará cómo usar Kotlin Coroutines . Para que funcione, necesitamos agregar la siguiente dependencia en nuestro build.gradlearchivo:

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

Esta biblioteca que utilizamos se llama Módulo kotlinx-coroutines-play-services y se utiliza exactamente para el mismo propósito. Como ya sabemos, no hay forma de que podamos devolver una lista de objetos como resultado de un método porque get()regresa inmediatamente, mientras que la devolución de llamada de la Tarea que devuelve se llamará en algún momento más tarde. Ésa es la razón por la que deberíamos esperar hasta que los datos estén disponibles.

Al llamar a "get()" en el Taskobjeto que se devuelve, podemos adjuntar un detector para poder obtener el resultado de nuestra consulta. Lo que debemos hacer ahora es convertir esto en algo que funcione con Kotlin Coroutines. Para eso, necesitamos crear una función de suspensión similar a esta:

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

Como puede ver, ahora tenemos una función de extensión llamada await()que interrumpirá la rutina hasta que los datos de la base de datos estén disponibles y luego los devolverá. Ahora podemos simplemente llamarlo desde otro método de suspensión como en las siguientes líneas de código:

private suspend fun getDataFromFirestore() {
    try {
        val listOfPlaces = getListOfPlaces()
    } catch (e: Exception) {
        Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
    }
}
Alex Mamo avatar Dec 01 '2019 10:12 Alex Mamo

Alex Mamo respondió perfectamente el motivo de tener una lista vacía arriba.

Simplemente me gusta presentar lo mismo sin necesidad de agregar nada extra interface.

En Kotlin podrías implementarlo así:

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)
        }
    }
}

y luego usarlo así:

readData() {
   Log.d("TAG", it.size.toString())
})
FabZbi avatar Mar 31 '2019 09:03 FabZbi