kotlin 为什么我的CoroutineScope不停止代码,直到它有Firebase数据并完成?

rbl8hiat  于 2022-12-13  发布在  Kotlin
关注(0)|答案(1)|浏览(151)

我创建了一个CoroutineScope来从Firebase获取数据,然后展开一个Card并在listView中显示该数据。但是当CoroutineScope仍在从Firebase获取数据并试图显示带有空列表的listView时,Card展开了。
下面是OnClickListener中的Expand函数(StartPageActivity.customClassList是之前定义的Object中的List):

var list : List<CustomClass> = ArrayList()

        CoroutineScope(Dispatchers.IO).launch {
            var customList: List<CustomClass> = ArrayList()
            StartPageActivity.customClassExpandList.forEach {
                if (it.title.contains(CustomClass.title)) {
                    customList += getFirebaseData(it.date)
                    if (customList.size == 12) {
                        list = customList
                    }
                }
            }
        }

        val listAdapter = MyListAdapter(context, list)

        listView.adapter = listAdapter

        listView.visibility = View.VISIBLE
        listView.dividerHeight = 0
        listView.layoutParams.height = calculateHeight(listView, 12)

下面是我的getFirebaseData函数:

suspend fun getFirebaseDate(date : LocalDate) : CustomClass = withContext(Dispatchers.IO){
    val customClass = CustomClass("$date", date, "Empty", false)
             FirebaseFirestore.getInstance().collection("users").document(FirebaseAuth.getInstance().currentUser!!.uid).collection("customClass")
        .document(date.toString()).get().addOnCompleteListener { task ->
            if (task.isSuccessful) {
                val document = task.result
                if (document.exists()) {
                    goal.description = document["description"].toString()
                    goal.title = document["tile"].toString()
                }
            }
        }.await()
    return@withContext customClass
}

getFirebaseData函数起作用并返回customClass;这也被添加到我的customList中。但是当代码试图用列表构建扩展的listView时会发生这种情况,它在CoroutineScope之前启动。
我试图在该范围内运行CoroutineScope之后的代码,但它不允许这样做,并返回了一个错误。
我也尝试添加多个暂停功能,但这也没有解决我的问题。
我也试着把forEach函数放在一个单独的suspend函数中,但我的问题仍然发生了。

bkhjykvo

bkhjykvo1#

要获得您所期望的行为,您需要使用runBlocking而不是CoroutineScope(Dispatchers.IO).launch。但是,这不是一个可行的网络操作解决方案。整个设备将完全冻结用户输入和屏幕刷新/动画,直到协程完成。如果网络操作花费超过几毫秒,这将是用户无法接受的。因为他们甚至无法在改变主意时退出您的应用程序,还有许多其他潜在问题。
你必须设计你的应用来优雅地处理一个网络操作可能需要一段时间的事实。这通常是通过在UI中显示某种加载指示符来完成的,然后你可以启动你的协同程序,然后 * 在协同程序内 * 当结果准备好时,你可以更新列表视图。
使用协程还有另外两个问题:
1.你不应该创建一个这样的新CoroutineScope。它会泄漏你的代码片段。使用viewLifecycleOwner.lifecycleScope.launch。不要使用Dispatchers.IO,因为如果你在你的协程中开始使用UI,那会导致故障和崩溃。你不需要Dispatchers.IO,除非你 * 直接 * 调用你的协程中的阻塞代码,如果你这样做了,您应该只使用withContext(Dispatchers.IO) { } Package 协程的那些部分。
1.在你的suspend函数中,你不应该混合使用监听器和await()。这是复杂的和容易出错的。你也忽略了失败的条件。如果你在使用await()的时候没有使用try/catch,你就有崩溃或者未处理错误的风险(这取决于你的协程作用域是如何设置的)。顺便说一句,您不需要在这里指定Dispatchers.IO(就像我上面提到的)因为await()不是一个阻塞函数。它是一个挂起函数。你的函数应该看起来像这样。I'我猜您正在使用CustomClass来传达检索是否成功。

suspend fun getFirebaseDate(date : LocalDate) : CustomClass = try {
    val document = FirebaseFirestore.getInstance()
        .collection("users")
        .document(FirebaseAuth.getInstance().currentUser!!.uid)
        .collection("customClass")
        .document(date.toString())
        .await()
    require(document.exists()) // Throw exception if it doesn't. Catch block will handle it.
    goal.description = document["description"].toString()
    goal.title = document["tile"].toString()
    CustomClass("$date", date, "Empty", false)
} catch (e: Exception) {
    CustomClass("$date", date, "Empty", true)
}

相关问题