作为 Kotlin 中的函数的结果,如何从 Firestore 数据库返回列表?

Posted

技术标签:

【中文标题】作为 Kotlin 中的函数的结果,如何从 Firestore 数据库返回列表?【英文标题】:How to return a list from Firestore database as a result of a function in Kotlin? 【发布时间】:2022-01-06 04:54:36 【问题描述】:

我正在为朋友构建一个应用程序,我使用 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;

如果我尝试打印,假设onCreate函数中列表的大小,大小始终为0。

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

我可以确认 Firebase 已成功安装。 我错过了什么?

【问题讨论】:

只需检查 this 即可。 谢谢,但它是针对 Java 的,我不懂 Java,我学习 Kotlin。你能在 Kotlin 中解决这个问题吗? 好的,我会用 Kotlin 给你写一个答案。 【参考方案1】:

这是异步 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;

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

在附加监听器之前!

附加监听器后!

onComplete 函数内部!

这可能不是您所期望的,但它准确地解释了为什么您的地点列表在返回时是空的。

大多数开发人员的最初反应是尝试“修复”这个asynchronous behavior,我个人建议不要这样做。 Here 是 Doug Stevenson 写的一篇很棒的文章,我强烈推荐你阅读。

此问题的快速解决方案是仅在 onComplete 函数内使用地点列表:

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
        
    

如果你想在外面使用列表,还有另一种方法。您需要创建自己的回调以等待 Firestore 向您返回数据。为此,首先您需要像这样创建一个interface

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

然后你需要创建一个函数来实际从数据库中获取数据。此方法应如下所示:

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)
        
    

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

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

如果您使用的是 Kotlin,请查看其他 answer。

【讨论】:

好的,让我知道。 太棒了,它奏效了。感谢您的精彩解释! 我也看过那篇文章的视频,Java 几乎是一样的。 感谢@AlexMamo 的出色回答和解释,它总是很有帮助。哪一种是从异步 API 中获取结果的最佳方式,方法是使用回调或使用 runBlocking 线程,然后使用异步函数返回结果。 @deepakkumar 使用回调。【参考方案2】:

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

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

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

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

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

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

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

【讨论】:

placesRef 是什么? @Rafael 这是一个完全指向places 集合的CollectionReference 对象。【参考方案3】:

上面的 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)
        
    

然后像这样使用它:

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

【讨论】:

非常精简和干净的解决方案。像魅力一样工作。 错误显示,For-loop 范围必须有一个 'iterator()' 方法

以上是关于作为 Kotlin 中的函数的结果,如何从 Firestore 数据库返回列表?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 数据类中的函数作为参数导致打包错误

如何从firebase Android kotlin获取基于userId的数据?

将文件上传到 Firebase 存储并获取 downloadUrl。如何在 Kotlin 函数中返回结果?

如何从kotlin中的类实例化对象

从 kotlin 中的 fun 返回 null

Kotlin函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )