Передача MutableLiveData между функциями

У меня есть функция «A» в ViewModel, которая извлекает данные из firebase, и я присваиваю значение MutableLiveData<Int> (все это заключено в onSuccessListener) и возвращаю его. Эта функция вызывается из другой функции «B» в той же ViewModel. Но когда я пытаюсь вернуть MutableLiveData<Int> из 'A', он возвращается как 0 (значение по умолчанию). Но если я назначу значение для MutableLiveData<Int> вне onSuccessListener, то значение будет возвращено.

Код:

val num = MutableLiveData<Int>().default(0)

private fun A():Int {
   FirebaseOperation
     .addOnSuccessListener{  //it:DocumentSnapshot!
         num.value = it.num
     }
   return num.value.toInt() // outside onsuccesslistener, default value 0 is being returned
}

private fun B() {
   val num2 = A()
}

Обновление: узнав об аде обратного вызова firebase, я переключился на сопрограмму kotlin для firebase (реализовав org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1 как зависимость) Обновленный код:

private suspend fun A():DocumentSnapshot? {
     return Firebase.firestore.collection("collection").documet("document").get().await()
    }

private suspend fun B(): Int{
    val data = A()
    val user = data.toObject<User>()
    val num = user.num
    return num
}

Но основной поток зависает, и приложение вылетает с ошибкой Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 9.).


person Anirudh Ganesh    schedule 08.06.2020    source источник


Ответы (6)


Если вы используете kotlinx.coroutines, вы можете использовать suspendCoroutine.

private suspend fun A(): Int = suspendCoroutine { cont ->
    FirebaseOperation.addOnSuccessListener{  // it: DocumentSnapshot!
        cont.resume(it.num)
    }
}

private suspend fun B() {
   val num2 = A()
}
person Animesh Sahu    schedule 08.06.2020
comment
Поэтому я переключился на сопрограммы, и, поскольку я использую Firestore, я использую зависимость org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1 и использую Firebase.firestore.collection("collection").documet("document").get().await(), но основной поток просто зависает и падает. - person Anirudh Ganesh; 09.06.2020
comment
@AnirudhGanesh есть ли какие-либо журналы сбоев, зарегистрированные на logcat или устройстве? - person Animesh Sahu; 09.06.2020
comment
@AnirudhGanesh может произойти, если поток пользовательского интерфейса (Dispatchers.Main) используется для длительных задач, попробуйте сопрограмму, вызывающую B(), переопределить диспетчер контекста по умолчанию или IO, например: scope.launch(Dispatchers.IO) { /* B() */ } - person Animesh Sahu; 09.06.2020

Извлечение данных из firebase происходит асинхронно или, другими словами, обратный вызов addOnSuccessListener вызывается только тогда, когда данные извлекаются из firebase. Таким образом, livedata обновляется только при вызове обратного вызова. когда вы вызываете A() из B(), он возвращает 0, потому что он не ждет обновления живых данных (вызов обратного вызова), а просто возвращает значение по умолчанию.

Когда вы просто обновляете значение livedata за пределами addOnSuccessListener, оно обновляется синхронно, и, следовательно, вы получаете обновленный результат.

person shubham vashisht    schedule 08.06.2020
comment
Так есть ли способ дождаться получения данных? Если я удалю onsuccesslistener, произойдет сбой с сообщением, что задача еще не завершена. - person Anirudh Ganesh; 08.06.2020
comment
@ Анирудх Ганеш, ты используешь сопрограммы? - person Animesh Sahu; 08.06.2020
comment
@AnirudhGanesh вместо того, чтобы возвращать Int, вы должны вернуть сами livedata и наблюдать за ними, вызывая observe(), и всякий раз, когда значение livedata будет обновляться, вы получите обратный вызов на своем наблюдателе. - person shubham vashisht; 09.06.2020

Основная цель LiveData — обновляться после асинхронной работы и наблюдаться. В функции A() я вижу, что вы добавляете только SuccessListener, но никакая операция не выполняется, поэтому SuccessListener не вызывается. В функции B() вы просто получаете ссылку на MutableLiveData, но не наблюдаете ее, поэтому она всегда содержит значение по умолчанию. Обычно LiveData объекты наблюдаются в Activity или Fragment:

num.observe(lifecycleOwner, androidx.lifecycle.Observer {
    // here you retrieve changes of your "num" variable as parameter "it"
})

где lifecycleOwner - обычно активность или фрагмент.

person Sergey    schedule 08.06.2020
comment
Я выполняю операции firebase, и возвращается моментальный снимок данных, который назначается номеру - person Anirudh Ganesh; 08.06.2020

Способ получить значение в функции B без использования сопрограмм и без наблюдения за живыми данными по жизненному циклу (что, кстати, вы не можете сделать, поскольку у вас нет владельца жизненного цикла в модели представления, и вы должны воздерживаться от его передачи), заключается в использовании MediatorLiveData.

Основная цель живых данных посредника состоит в том, что они зависят от других живых данных из-за их значения и могут наблюдать другие живые данные без жизненного цикла.

Таким образом, вы можете создать MediatorLiveData внутри функции B, затем вернуть оперативные данные из функции A, а затем наблюдать за ними, используя MediatorLiveData в функции B. Таким образом, когда значение будет обновлено в A, вы будете уведомлены в B.

Подробнее о MediatorLiveData можно прочитать здесь: https://developer.android.com/reference/android/arch/lifecycle/MediatorLiveData

person Devansh Maurya    schedule 10.06.2020

У вас может быть поток или вы можете напрямую кормить свою виртуальную машину.

class YourViewModel:ViewModel(){

val num = MutableLiveData<Int>().default(0)

   private suspend fun A():DocumentSnapshot? {
     return Firebase.firestore.collection("collection").documet("document").get().await()
   }

   fun onSomeButtonClick(){
       viewModelScope.launch(Main){
          val num =  A()?.toObject<User>().num ?: return
          num.value = num
       }
   } 

}

или flow с расширением asLiveData.

val num:LiveData<Int> = flow{
   val num =  A()?.toObject<User>()?.num ?: 0
   emit(num)
}.asLiveData()

В своем UI вы можете наблюдать и получать id.

person ibrahimyilmaz    schedule 10.06.2020

Мне каким-то образом удалось объединить обе функции, и теперь я вложил все обратные вызовы БД. Я не думаю, что это хорошо для читаемости кода. Пока буду пробовать другие варианты.

person Anirudh Ganesh    schedule 11.06.2020