Наблюдение за LiveData из ViewModel

91

У меня есть отдельный класс, в котором я обрабатываю выборку данных (в частности, Firebase), и я обычно возвращаю из него объекты LiveData и обновляю их асинхронно. Теперь я хочу, чтобы возвращенные данные хранились в ViewModel, но проблема в том, что для получения указанного значения мне нужно наблюдать за объектом LiveData, возвращаемым из моего класса выборки данных. Для метода наблюдения требуется объект LifecycleOwner в качестве первого параметра, но у меня, очевидно, нет этого внутри моей ViewModel, и я знаю, что я не должен хранить ссылку на Activity / Fragment внутри ViewModel. Что я должен делать?

Вук Бибич
источник
Относится

Ответы:

38

В этом сообщении блога разработчика Google Хосе Альсерреки рекомендуется использовать преобразование в этом случае (см. Параграф «LiveData в репозиториях»), потому что ViewModel не должен содержать никаких ссылок, связанных с View(Activity, Context и т. Д.), Потому что это затрудняет тестировать.

гуглхупф
источник
Вам удалось заставить Трансформацию работать на вас? Мои мероприятия не работают
Романезо
23
Сами по себе преобразования не работают, поскольку любой код, который вы пишете в преобразовании, присоединяется к запуску только тогда, когда какой-либо объект наблюдает за преобразованием .
orbitbot
5
Я не знаю, почему это рекомендуемый ответ, это не имеет ничего общего с вопросом. 2 года спустя, а мы до сих пор не знаем, как отслеживать изменения данных репозитория в нашей модели просмотра.
Эндрю
24

В документации ViewModel

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

Другой способ - реализовать RxJava вместо LiveData для данных, тогда у них не будет преимуществ, связанных с жизненным циклом.

В примере Google todo-mvvm-live-kotlin используется обратный вызов без LiveData в ViewModel.

Я предполагаю, что если вы хотите полностью соответствовать идее того, чтобы быть продуктом жизненного цикла, нам нужно переместить код наблюдения в Activity / Fragment. В противном случае мы можем использовать обратный вызов или RxJava в ViewModel.

Другой компромисс - реализовать MediatorLiveData (или Transformations) и наблюдать (разместить здесь свою логику) в ViewModel. Обратите внимание, что наблюдатель MediatorLiveData не сработает (как и преобразования), если он не будет обнаружен в Activity / Fragment. Что мы делаем, так это помещаем пустое наблюдение в Activity / Fragment, где реальная работа фактически выполняется в ViewModel.

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PS: Я прочитал ViewModels и LiveData: Patterns + AntiPatterns, в которых говорилось, что Transformations. Я не думаю, что это сработает, если не соблюдаются LiveData (что, вероятно, требует, чтобы это было сделано в Activity / Fragment).

Десмонд Луа
источник
2
Что-то изменилось в этом плане? Или RX, обратный вызов или пустое наблюдение - это только решения?
qbait
2
Есть ли способ избавиться от этих пустых наблюдений?
Ehsan Mashhadi
1
Возможно, используя Flow ( mLiveData.asFlow()) или observeForever.
Machado
Решение Flow, похоже, работает, если вы не хотите иметь / вам не нужна логика наблюдателя во фрагменте
adek111
14

Я думаю, вы можете использовать ObserverForever, который не требует интерфейса владельца жизненного цикла, и вы можете наблюдать результаты из модели просмотра.

Сиддхарт
источник
2
это кажется мне правильным ответом, особенно если в документации о ViewModel.onCleared () сказано: «Это полезно, когда ViewModel наблюдает за некоторыми данными, и вам нужно очистить эту подписку, чтобы предотвратить утечку этой ViewModel».
Йосеф
2
Извините, ноCannot invoke observeForever on a background thread
Бокен
1
Это кажется вполне законным. Хотя нужно сохранить наблюдателей в полях viewModel и отказаться от подписки по адресу onCleared. Что касается фонового потока - наблюдайте из основного потока, вот и все.
Кирилл Старостин
@Boken Вы можете принудительно observeForeverвызывать из основного черезGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle
4

Используйте сопрограммы Kotlin с компонентами архитектуры.

Вы можете использовать liveDataфункцию построителя для вызова suspendфункции, обслуживающей результат как LiveDataобъект.

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

Вы также можете передать несколько значений из блока. Каждый emit()вызов приостанавливает выполнение блока до тех пор, пока LiveDataзначение не будет установлено в основном потоке.

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

В вашей конфигурации gradle используйте androidx.lifecycle:lifecycle-livedata-ktx:2.2.0или выше.

Об этом тоже есть статья .

Обновление : также можно изменить LiveData<YourData>в Dao interface. К функции нужно добавить suspendключевое слово:

@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>

и в нем ViewModelвам нужно получить его асинхронно вот так:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}
Псиджик
источник