Наблюдение за 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
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.