如何通过Kotlin中的函数返回Firestore数据库中的列表?

Jan*_*ley 3 kotlin firebase google-cloud-firestore

我正在为朋友构建一个应用程序,我使用的是Firestore.我想要的是显示最喜欢的地方列表,但由于某种原因,列表总是空的.

在此输入图像描述

我无法从Firestore获取数据.这是我的代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果我尝试打印,请在onCreate函数中说明列表的大小,大小始终为0.

Log.d("TAG", getListOfPlaces().size().toString()); // Is 0 !!!
Run Code Online (Sandbox Code Playgroud)

我可以确认Firebase已成功安装.如果您需要我的其他信息,请告诉我.我错过了什么?谢谢!

Ale*_*amo 10

这是异步Web API的经典问题.您现在无法返回尚未加载的内容.换句话说,您不能简单地返回places作为方法结果的列表,因为由于onComplete函数的异步行为,它总是为空.根据您的连接速度和状态,可能需要几百毫秒到几秒才能获得数据.

但是,不仅Cloud Firestore异步加载数据,几乎所有现代其他Web API都会这样做,因为获取数据可能需要一些时间.但是,让我们举一个简单的例子,在代码中放置一些日志语句,以便更清楚地看到我在说什么.

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;
}
Run Code Online (Sandbox Code Playgroud)

如果我们运行此代码,logcat中的输出将是:

在连接听众之前!

附上听众后!

里面的onComplete函数!

这可能不是您所期望的,但它解释了为什么返回时您的地方列表为空.

大多数开发人员的初始响应是尝试"修复"这个asynchronous behavior,我个人建议反对它.以下是Doug Stevenson撰写的一篇优秀文章,我强烈建议您阅读.

快速解决此问题的方法是仅在onComplete函数内部使用places列表:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想在外面使用列表,还有另一种方法.您需要创建自己的回调以等待Firestore返回数据.要实现这一点,首先需要创建interface如下:

interface MyCallback {
    fun onCallback(value: List<String>)
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要创建一个实际从数据库获取数据的函数.此方法应如下所示:

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

看,我们不再有任何返回类型.最后,只需readData()onCreate函数中调用函数,并将MyCallback接口实例作为参数传递,如下所示:

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

  • 太棒了,它奏效了。感谢您的精彩解释! (2认同)
  • 感谢@AlexMamo 的精彩回答和解释,它总是有很大帮助。哪一种是通过使用回调或使用 runBlocking 线程从异步 API 获取结果然后使用 async 函数返回结果的最佳方法。 (2认同)

Ale*_*amo 9

如今,Kotlin 提供了一种更简单的方法来实现与使用回调相同的结果。这个答案将解释如何使用Kotlin Coroutines。为了使它工作,我们需要在我们的build.gradle文件中添加以下依赖项:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.2.1"
Run Code Online (Sandbox Code Playgroud)

我们使用的这个库称为模块 kotlinx-coroutines-play-services,用于完全相同的目的。正如我们已经知道的那样,我们无法将对象列表作为方法的结果get()返回,因为它立即返回,而它返回的 Task 的回调将在稍后调用。这就是为什么我们应该等到数据可用的原因。

Task返回的对象上调用“get()”时,我们可以附加一个侦听器,以便获得查询的结果。我们现在需要做的是将其转换为可与 Kotlin 协程配合使用的内容。为此,我们需要创建一个如下所示的挂起函数:

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

如您所见,我们现在调用await()了一个扩展函数,它将中断协程,直到数据库中的数据可用,然后返回它。现在我们可以简单地从另一个挂起方法调用它,如以下代码行:

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


Fab*_*Zbi 5

上面的 Alex Mamo 完美地回答了空列表的原因。

我只是想呈现同样的东西而不需要添加额外的东西interface

在 Kotlin 中你可以像这样实现它:

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

readData() {
   Log.d("TAG", it.size.toString())
})
Run Code Online (Sandbox Code Playgroud)