Функция приостановки может быть вызвана только в теле сопрограммы. - PullRequest
1 голос
/ 24 февраля 2020

Я пытаюсь доставлять в реальном времени обновления для моего вида с помощью 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 ]

2 голосов
/ 24 февраля 2020

Слушатель моментального снимка Firestore представляет собой асинхронный обратный вызов, который выполняется в другом потоке, который не имеет ничего общего с потоками сопрограмм, управляемыми Kotlin. Вот почему вы не можете вызвать emit() внутри асинхронного обратного вызова - обратный вызов просто не находится в контексте сопрограммы, поэтому он не может приостановиться как сопрограмма.

Для того, что вы пытаетесь сделать, требуется, чтобы Вы помещаете свой вызов для передачи обратно в контекст сопрограммы, используя любой метод, который считаете нужным (например, launch), или, возможно, запускаете callbackFlow , который позволяет предлагать объекты из других потоков.

1 голос
/ 25 февраля 2020

Ключевое слово 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.

...