Функция приостановки может быть вызвана только в теле сопрограммы
Я пытаюсь обновлять свое представление в реальном времени с помощью 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 с помощью:
Функция приостановки 'emit' должна вызываться только из сопрограммы или другой функции приостановки
Но я вызываю этот код из CoroutineScope
в моем ViewModel
.
В чем проблема?
Спасибо
2 ответа
Слушатель моментальных снимков Firestore - это, по сути, асинхронный обратный вызов, который выполняется в другом потоке, не имеющем ничего общего с потоками сопрограмм, управляемыми Kotlin. Вот почему ты не можешь позвонитьemit()
внутри асинхронного обратного вызова - обратный вызов просто не находится в контексте сопрограммы, поэтому он не может приостанавливаться, как сопрограмма.
То, что вы пытаетесь сделать, требует, чтобы вы поместили свой вызов для передачи обратно в контекст сопрограммы, используя любой метод, который вы считаете подходящим (например, launch
) или, возможно, запустить callbackFlow, который позволяет вам предлагать объекты из других потоков.
В suspend
ключевое слово на getVersionCodeRepo()
не относится к emit(Resource.Success(versionCode!!.toInt()))
потому что он вызывается из лямбды. Поскольку вы не можете изменитьaddSnapshotListener
вам нужно будет использовать построитель сопрограмм, например launch
призвать suspend
функция.
Когда лямбда передается в функцию, объявление соответствующего параметра функции определяет, может ли она вызвать функцию приостановки без построителя сопрограмм. Например, вот функция, которая принимает параметр функции без аргументов:
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
лямбда вызывается так, как если бы она вызывалась внутри функции, объявленной как:
public abstract void onEvent (T value, FirebaseFirestoreException error)
Поскольку это объявление функции не имеет suspend
ключевое слово (не может, оно написано на Java), то любая переданная ему лямбда должна использовать построитель сопрограмм для вызова функции, объявленной с suspend
ключевое слово.