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

SNM*_*SNM 5 android kotlin firebase kotlin-coroutines kotlinx.coroutines.flow

我正在尝试使用 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是每当 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()))
            }
    }
Run Code Online (Sandbox Code Playgroud)

问题是,当我使用:

 emit(Resource.Success(versionCode!!.toInt()))
Run Code Online (Sandbox Code Playgroud)

Android Studio 突出显示了发出调用:

挂起函数“emit”只能从协程或其他挂起函数调用

但是我从CoroutineScope我的ViewModel.

这里有什么问题?

谢谢

Dou*_*son 8

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

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


roo*_*roo 5

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

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

fun f(g: () -> Unit)
Run Code Online (Sandbox Code Playgroud)

如果这个函数被这样调用:

f {
    // do something
}
Run Code Online (Sandbox Code Playgroud)

花括号中的所有内容都像在声明为的函数中一样执行:

fun g() {
    // do something
}
Run Code Online (Sandbox Code Playgroud)

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

但是,如果f()这样声明:

fun f(g: suspend () -> Unit)
Run Code Online (Sandbox Code Playgroud)

并被这样调用:

f {
    // do something
}
Run Code Online (Sandbox Code Playgroud)

花括号中的所有内容都像在声明为的函数中一样执行:

suspend fun g() {
    // do something
}
Run Code Online (Sandbox Code Playgroud)

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

addEventListenerlambda 被调用的情况下,就好像它是在声明为的函数中调用的:

public abstract void onEvent (T value, FirebaseFirestoreException error)
Run Code Online (Sandbox Code Playgroud)

由于此函数声明没有suspend关键字(它不能,它是用 Java 编写的),因此传递给它的任何 lambda 都必须使用协程构建器来调用使用suspend关键字声明的函数。