挂起函数只能在协程体内调用

Suspending function can only be called within coroutine body

我正在尝试使用 Kotlin Flows 和 Firebase 为我的视图提供实时更新。

这就是我从 ViewModel:

收集实时数据的方式
class MainViewModel(repo: IRepo): ViewModel() {

    val fetchVersionCode = liveData(Dispatchers.IO) {
        emit(Resource.Loading())

        try {
            repo.getVersionCode().collect {
                emit(it)
            }

        } catch (e: Exception){
            emit(Resource.Failure(e))
            Log.e("ERROR:", e.message)
        }
    }
}

这就是每当 Firebase 中的值发生变化时我从我的存储库中发出每个数据流的方式:

class RepoImpl: IRepo {

    override suspend fun getVersionCodeRepo(): Flow<Resource<Int>> = flow {

        FirebaseFirestore.getInstance()
            .collection("params").document("app").addSnapshotListener { documentSnapshot, firebaseFirestoreException ->
                val versionCode = documentSnapshot!!.getLong("version")
                emit(Resource.Success(versionCode!!.toInt()))
            }
    }

问题是当我使用:

 emit(Resource.Success(versionCode!!.toInt()))

Android Studio 通过以下方式突出显示 emit 调用:

Suspend function 'emit' should be called only from a coroutine or another suspend function

但我是从 ViewModel 中的 CoroutineScope 调用此代码的。

这里有什么问题?

谢谢

Firestore 快照侦听器实际上是一个异步回调,它在与 Kotlin 管理的协程线程无关的另一个线程上运行。这就是为什么你不能在异步回调中调用 emit() - 回调不在协程上下文中,所以它不能像协程一样挂起。

您要尝试做的事情要求您使用您认为合适的任何方法(例如 launch)将调用发送回协程上下文,或者启动一个 callbackFlow让您提供来自其他线程的对象。

getVersionCodeRepo() 上的 suspend 关键字不适用于 emit(Resource.Success(versionCode!!.toInt())),因为它是从 lambda 中调用的。由于您无法更改 addSnapshotListener,因此您需要使用 launch 等协程构建器来调用 suspend 函数。

当 lambda 传递给函数时,其相应函数参数的声明决定了它是否可以在没有协程构建器的情况下调用挂起函数。例如,这是一个采用无参数函数参数的函数:

fun f(g: () -> Unit)

如果这个函数是这样调用的:

f {
    // do something
}

花括号内的所有内容都被执行,就好像它在一个函数中一样,声明为:

fun g() {
    // do something
}

由于 g 没有使用 suspend 关键字声明,它不能在不使用协程构建器的情况下调用 suspend 函数。

然而,如果f()这样声明:

fun f(g: suspend () -> Unit)

并这样称呼:

f {
    // do something
}

花括号内的所有内容都被执行,就好像它在一个函数中一样,声明为:

suspend fun g() {
    // do something
}

由于gsuspend关键字声明的,它可以调用suspend 不使用协程构建器的功能。

addEventListener 的情况下,调用 lambda 就像在声明为以下的函数中调用它一样:

public abstract void onEvent (T value, FirebaseFirestoreException error)

由于这个函数声明没有suspend关键字(不能,写在Java中)那么任何传递给它的lambda都必须使用coroutine builder来调用函数用 suspend 关键字声明。