'awaitClose { yourCallbackOrListener.cancel() }' 应在 callbackFlow 块的末尾使用。Kotlin 流程中的错误

'awaitClose { yourCallbackOrListener.cancel() }' should be used in the end of callbackFlow block. Error in Kotlin flow

提问人:heet kanabar 提问时间:6/20/2023 最后编辑:heet kanabar 更新时间:6/29/2023 访问量:361

问:

我有一个应用程序,用户可以上传他们的壁纸并下载它,并且可以看到它,所以当用户登录并为了从 firestore 获取用户的详细信息时,我有这个功能,我将获取用户的详细信息:-

override suspend fun getUserDetails(userId: String): Flow<Response<User>> = callbackFlow {
    val listener = fireStore.collection(Consts.USERS_COLLECTION_NAME)
        .document(userId)
        .addSnapshotListener { snapShot, error ->
            val result = if (snapShot != null) {
                val userInfo = snapShot.toObject(User::class.java)
                Response.Success(userInfo!!)
            } else {
                Response.Error(error?.message ?: error.toString())
            }
            trySend(result)
        }
//        awaitClose()

    awaitClose {
        this.cancel()
        listener.remove()
        close()
    }
}

我收到这个错误:-

java.lang.IllegalStateException: 'awaitClose { yourCallbackOrListener.cancel() }' 应该在 callbackFlow 块。

我看到了很多关于它的问题,我尝试过聊天 gpt 的代码,但一次又一次地遇到同样的错误。

我的观点模型:-

viewModelScope.launch {
        if (!firebaseUser?.isAnonymous!!){
            userRepo.getUserDetails(firebaseUser.uid).collect{
                when(it){
                    is Response.Error -> sendUIEvents(UIEvents.ShowSnackBar(it.message))
                    is Response.Loading -> Unit
                    is Response.Success -> {
                        // Some code
                               
                    }
                }
            }
        }
    }

我的用户流程是这样的:-

初始屏幕 > 主屏幕 > 个人资料屏幕 > 上传壁纸屏幕

注意:-上面的代码位于主屏幕和配置文件屏幕中,我在主屏幕中收到此错误。

如果您有任何可能的答案,那么您可以发布它,还有一件事,我在不同的文件中有非常精确的代码,甚至没有更改变量名称,并且我没有收到任何错误,我只从此代码中收到错误。谢谢。

我的全栈:-

FATAL EXCEPTION: main Process: com.example.wallz, PID: 709 java.lang.IllegalStateException: 'awaitClose { yourCallbackOrListener.cancel() }' should be used in the end of callbackFlow block. Otherwise, a callback/listener may leak in case of external cancellation. See callbackFlow API documentation for the details. at kotlinx.coroutines.flow.CallbackFlowBuilder.collectTo(Builders.kt:343) at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69) at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:245) at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161) at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397) at kotlinx.coroutines.CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:513) at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive(AbstractChannel.kt:908) at kotlinx.coroutines.channels.ArrayChannel.offerInternal(ArrayChannel.kt:83) at kotlinx.coroutines.channels.AbstractSendChannel.trySend-JP2dKIU(AbstractChannel.kt:155) at kotlinx.coroutines.channels.ChannelCoroutine.trySend-JP2dKIU(Unknown Source:2) at com.example.wallz.data.repositories.UserRepoImpl$getUserDetails$2.invokeSuspend$lambda-0(UserRepoImpl.kt:109) at com.example.wallz.data.repositories.UserRepoImpl$getUserDetails$2.$r8$lambda$BnGWgnacAYeEFR-hHCZ0JzY-fc0(Unknown Source:0) at com.example.wallz.data.repositories.UserRepoImpl$getUserDetails$2$$ExternalSyntheticLambda0.onEvent(Unknown Source:4) at com.google.firebase.firestore.DocumentReference.lambda$addSnapshotListenerInternal$2$com-google-firebase-firestore-DocumentReference(DocumentReference.java:504) at com.google.firebase.firestore.DocumentReference$$ExternalSyntheticLambda2.onEvent(Unknown Source:6) at com.google.firebase.firestore.core.AsyncEventListener.lambda$onEvent$0$com-google-firebase-firestore-core-AsyncEventListener(AsyncEventListener.java:42) at com.google.firebase.firestore.core.AsyncEventListener$$ExternalSyntheticLambda0.run(Unknown Source:6) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8751) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@b3d76f0, Dispatchers.Main.immediate]

Android kotlin kotlin-flow asynccallback

评论

0赞 Tenfour04 6/20/2023
您可以使用返回 Flow 的预先存在的扩展函数之一,然后映射流,更简洁地构建此函数。firebase.google.com/docs/reference/kotlin/com/google/firebase/......

答:

0赞 Tenfour04 6/20/2023 #1

您不得从内部致电或从内部致电。当您的 lambda in 被调用时,协程已经取消或通道关闭,因此它是多余的。cancel()close()awaitCloseawaitClose

callbackFlow旨在在调用失败时抛出此异常,但它们检测到未调用它的方式也是由意外调用冗余或 触发的。因此,不幸的是,它不会在异常中为您提供有用的错误消息。awaitClosecancel()close()

评论

0赞 heet kanabar 6/20/2023
所以我已经尝试了没有取消和关闭的代码,但它不起作用,给了我同样的错误,所以你能纠正我在代码中的错误吗,我也想知道为什么会发生这种情况?
0赞 Tenfour04 6/20/2023
你能把完整的堆栈跟踪添加到你的问题中吗?
0赞 heet kanabar 6/20/2023
您好,我刚刚用堆栈跟踪编辑了我的问题,您会看吗?
0赞 Tenfour04 6/20/2023
尝试在外部 lambda 之后添加。.flowOn(Dispatchers.Main)
0赞 heet kanabar 6/21/2023
很抱歉地说,但它不起作用。我尝试了 try catch,但它给了我同样的错误。
0赞 Atick Faisal 6/20/2023 #2

首先,有几件事:

  1. 由于您返回的是流,因此不需要将函数设为函数。suspend
  2. 您不需要在块中使用 和。cancel()close()awaitClose {}

要修复此错误,请确保您使用的是正确的导入:

import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow

评论

0赞 heet kanabar 6/20/2023
感谢您的回答,我已经添加了正确的导入。还有其他解决方案吗?
0赞 heet kanabar 6/29/2023 #3

这是我的解决方案,也是我经过长期研究后发现的:-

override suspend fun getUserDetails(userId: String): Flow<Response<User>> = callbackFlow {
    try {
        val snapshot = fireStore.collection(Consts.USERS_COLLECTION_NAME)
            .document(userId)
            .get()
            .await()

        val user = snapshot.toObject(User::class.java)

        val result = if (user != null) {
            Response.Success(user)
        } else {
            Response.Error("Could not get user's data.")
        }
        trySend(result)
        close()
        awaitCancellation()
    } catch (e: Exception) {
        trySend(Response.Error(e.message ?: e.toString()))
    }
}.flowOn(Dispatchers.IO)

这是从firebase获取数据的正确方法,这不会产生任何错误和任何异常。

在这里,我在代码中使用了函数和函数,最后我使用了函数。get()await()awaitCancellation()

评论

0赞 Tenfour04 6/29/2023
我希望这将失败并抛出 IllegalStateException,如果它到达您的块。需要移动并在尝试/捕获之外。但是,这是不必要的用法,因为这是针对反复发出某些东西的听众。您应该只使用构建器并简单地发出您的项目,尽管实际上这甚至不应该是 Flow,因为它不会发出多个项目。这与原始代码尝试的操作不同(使用在数据库更改时重复发出对象的侦听器)。catchclose()awaitCancellation()callbackFlowcallbackFlowflow
0赞 heet kanabar 7/1/2023
明白你的观点,我同意你的看法,我使用回调是错误的,但我已经将其切换到流程。谢谢。但我想知道我们如何在没有任何流程或回调的情况下传递数据?
0赞 Tenfour04 7/1/2023
一个只返回数据的挂起函数。或者,如果要预取或从多个位置调用函数,并且不希望必须重新获取数据,则返回 Deferred。
0赞 heet kanabar 7/1/2023
是的,你是对的。