Obserwowanie LiveData z ViewModel

90

Mam osobną klasę, w której zajmuję się pobieraniem danych (w szczególności Firebase) i zazwyczaj zwracam z niej obiekty LiveData i aktualizuję je asynchronicznie. Teraz chcę, aby zwrócone dane były przechowywane w ViewModel, ale problem polega na tym, że aby uzyskać tę wartość, muszę obserwować obiekt LiveData zwrócony z mojej klasy pobierania danych. Metoda observ wymagała obiektu LifecycleOwner jako pierwszego parametru, ale oczywiście nie mam tego w moim ViewModel i wiem, że nie powinienem zachowywać odniesienia do Activity / Fragment wewnątrz ViewModel. Co powinienem zrobić?

Vuk Bibic
źródło

Odpowiedzi:

38

W tym poście na blogu autorstwa programisty Google Jose Alcérreca zaleca się użycie transformacji w tym przypadku (zobacz akapit „LiveData w repozytoriach”), ponieważ ViewModel nie powinien zawierać żadnych odniesień związanych z View(Aktywnością, Kontekstem itp.), Ponieważ utrudniało to testować.

guglhupf
źródło
czy udało ci się zmusić Transformację do pracy? Moje wydarzenia nie działają
romaneso
23
Transformacje same w sobie nie działają, ponieważ kod, który napiszesz w transformacji, jest dołączany do uruchomienia tylko wtedy, gdy jakaś jednostka obserwuje transformację .
orbitbot
5
Nie wiem, dlaczego jest to zalecana odpowiedź, nie ma to nic wspólnego z pytaniem. Dwa lata później nadal nie wiemy, jak obserwować zmiany danych repozytorium w naszym modelu widoku.
Andrew,
24

W dokumentacji ViewModel

Jednak obiekty ViewModel nigdy nie mogą obserwować zmian obserwowalnych uwzględniających cykl życia, takich jak obiekty LiveData.

Innym sposobem jest implementacja danych RxJava zamiast LiveData, wtedy nie będzie miała korzyści z uwzględnienia cyklu życia.

W Google próbce todo-mvvm-live-kotlin używa wywołania zwrotnego bez LiveData w ViewModel.

Domyślam się, że jeśli chcesz zachować zgodność z całą ideą bycia produktem cyklu życia, musimy przenieść kod obserwacji w Activity / Fragment. W przeciwnym razie możemy użyć funkcji callback lub RxJava w ViewModel.

Innym kompromisem jest zaimplementowanie MediatorLiveData (lub Transformations) i obserwowanie (wstaw swoją logikę) w ViewModel. Zauważ, że obserwator MediatorLiveData nie zostanie wyzwolony (tak samo jak Transformacje), chyba że zostanie zaobserwowany w Activity / Fragment. To, co robimy, to umieszczamy pustą obserwację w Activity / Fragment, gdzie prawdziwa praca jest faktycznie wykonywana w 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: Czytałem ViewModels i LiveData: Patterns + AntiPatterns, które sugerowały, że Transformations. Nie sądzę, żeby to działało, chyba że obserwuje się LiveData (co prawdopodobnie wymaga wykonania w Activity / Fragment).

Desmond Lua
źródło
2
Czy coś się zmieniło w tym zakresie? Czy RX, oddzwonienie lub puste obserwacje to tylko rozwiązania?
qbait
2
Jakieś rozwiązanie, aby pozbyć się tych pustych obserwacji?
Ehsan Mashhadi
1
Może za pomocą Flow ( mLiveData.asFlow()) lub observeForever.
Machado
Rozwiązanie Flow wydaje się działać, jeśli nie chcesz mieć / nie potrzebujesz logiki obserwatora we fragmencie
adek111
14

Myślę, że możesz użyć followForever, która nie wymaga interfejsu właściciela cyklu życia i możesz obserwować wyniki z Viewmodel

siddharth
źródło
2
wydaje mi się, że jest to właściwa odpowiedź, zwłaszcza że w dokumentach dotyczących ViewModel.onCleared () jest napisane: „Jest to przydatne, gdy ViewModel obserwuje pewne dane i musisz wyczyścić tę subskrypcję, aby zapobiec wyciekowi tego ViewModel”.
Yosef
2
Przepraszam, aleCannot invoke observeForever on a background thread
Boken
1
Wydaje się to całkiem uzasadnione. Chociaż trzeba zapisywać obserwatorów w polach viewModel i wypisać się na onCleared. Jeśli chodzi o wątek tła - obserwuj z wątku głównego, to wszystko.
Kirill Starostin
@Boken Możesz wymusić observeForeverwywołanie z głównej viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle
4

Użyj coroutines Kotlin z komponentami architektury.

Możesz użyć liveDatafunkcji konstruktora, aby wywołać suspendfunkcję, wyświetlając wynik jako LiveDataobiekt.

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

Możesz również emitować wiele wartości z bloku. Każde emit()wywołanie zawiesza wykonanie bloku do momentu ustawienia LiveDatawartości w głównym wątku.

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

W swojej konfiguracji gradle użyj androidx.lifecycle:lifecycle-livedata-ktx:2.2.0lub nowszej.

Jest też artykuł na ten temat.

Aktualizacja : możliwa jest również zmiana LiveData<YourData>w Dao interface. Musisz dodać suspendsłowo kluczowe do funkcji:

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

iw tym ViewModelmusisz uzyskać to asynchronicznie w ten sposób:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}
Psijic
źródło