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

时间:2018-07-30 13:11:41

标签: firebase kotlin google-cloud-firestore

我正在为朋友构建应用程序,并且使用Firestore。我要显示的是最喜欢的地点列表,但由于某种原因,该列表始终为空。

enter image description here

我无法从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已成功安装。 我想念什么?

3 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:2)

上面列出的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())
})

答案 2 :(得分:0)

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

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

我们使用的这个库称为Module kotlinx-coroutines-play-services,其用途完全相同。众所周知,由于方法get()会立即返回,但由于方法的结果,我们无法返回对象列表,但是稍后将调用它返回的Task的回调。这就是我们要等到数据可用的原因。

在调用Task时返回的get()对象上,我们可以附加一个侦听器,以便获得查询的结果。我们现在需要做的就是将其转换为与Kotlin协程兼容的东西。为此,我们需要创建一个看起来像这样的暂停函数:

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