Спостереження LiveData з ViewModel


91

У мене є окремий клас, в якому я обробляю отримання даних (зокрема, Firebase), і я зазвичай повертаю з нього об’єкти LiveData та оновлюю їх асинхронно. Тепер я хочу, щоб повернуті дані зберігались у ViewModel, але проблема полягає в тому, що для отримання зазначеного значення мені потрібно спостерігати за об’єктом LiveData, повернутим із мого класу отримання даних. Метод спостереження вимагав об'єкта LifecycleOwner як перший параметр, але я, очевидно, не маю цього всередині мого ViewModel, і я знаю, що я не повинен зберігати посилання на Activity / Fragment всередині ViewModel. Що я повинен зробити?


Відповіді:


38

У цьому дописі в блозі розробника Google Хосе Алькрека рекомендується використовувати трансформацію в цьому випадку (див. Абзац "LiveData у сховищах"), оскільки ViewModel не повинен містити жодних посилань, пов’язаних з View(Діяльність, Контекст тощо), оскільки це ускладнило тестувати.


вам вдалося змусити трансформацію працювати для вас? Мої події не працюють
romaneso

24
Трансформації самі по собі не працюють, оскільки будь-який код, який ви пишете в трансформації, додається до запуску лише тоді, коли певна сутність спостерігає за трансформацією .
orbitbot

6
Не знаю, чому це рекомендована відповідь, це не має нічого спільного з питанням. Через 2 роки ми все ще не знаємо, як спостерігати за змінами даних сховища в нашій viewmodel.
Ендрю

24

У документації ViewModel

Однак об'єкти ViewModel ніколи не повинні спостерігати змін у спостережуваних за життєвим циклом, таких як об'єкти LiveData.

Інший спосіб полягає в тому, щоб дані реалізовували RxJava, а не LiveData, тоді вони не матимуть переваги знання життєвого циклу.

У зразку google todo-mvvm-live-kotlin він використовує зворотний виклик без LiveData у ViewModel.

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

Іншим компромісом є реалізація MediatorLiveData (або Трансформації) та спостереження (помістіть свою логіку тут) у 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, які пропонували перетворення. Я не думаю, що це працює, якщо не спостерігається LiveData (що, ймовірно, вимагає, щоб це було зроблено в Activity / Fragment).


2
Чи щось змінилося в цьому плані? Або RX, зворотний дзвінок або пусте спостереження - це лише рішення?
qbait

2
Будь-яке рішення, як позбутися цих порожніх спостережень?
Ехсан Машхаді,

1
Можливо, використовуючи Flow ( mLiveData.asFlow()) або observeForever.
Мачадо,

Здається, рішення рішення працює, якщо ви не хочете мати / вам не потрібна жодна логіка спостерігача у фрагменті
adek111,

14

Я думаю, ви можете використовувати obserForever, який не вимагає інтерфейсу власника життєвого циклу, і ви можете спостерігати результати з viewmodel


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.