我正在尝试使用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'
但是我是从CoroutineScope
中的ViewModel
调用此代码的。
这是什么问题?
谢谢
答案 0 :(得分:4)
Firestore快照侦听器实际上是一个异步回调,它在与Kotlin管理的协程线程无关的另一个线程上运行。这就是为什么您不能在异步回调中调用emit()
的原因-回调根本不在协程环境中,因此它不能像协程那样挂起。
您要执行的操作要求您使用合适的方法(例如,launch
)将呼叫放回协程上下文中,或者启动一个callbackFlow提供其他线程的对象。
答案 1 :(得分:3)
suspend
上的getVersionCodeRepo()
关键字不适用于emit(Resource.Success(versionCode!!.toInt()))
,因为它是在lambda中调用的。由于您无法更改addSnapshotListener
,因此需要使用诸如launch
之类的协程生成器来调用suspend
函数。
将lambda传递给函数时,其相应函数参数的声明决定了是否可以在没有协程生成器的情况下调用暂停函数。例如,这是一个使用no-arg函数参数的函数:
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
}
由于使用g
关键字声明了suspend
,因此可以调用suspend
函数,而无需使用协程生成器。
在addEventListener
的情况下,lambda的调用就像是在声明为以下函数的函数中被调用一样:
public abstract void onEvent (T value, FirebaseFirestoreException error)
由于此函数声明没有suspend
关键字(不能,它是用Java编写的),因此传递给它的任何lambda必须使用协程生成器来调用用{{1 }}关键字。