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ć?
90
Odpowiedzi:
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ć.źródło
W dokumentacji ViewModel
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).
źródło
mLiveData.asFlow()
) lubobserveForever
.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
źródło
Cannot invoke observeForever on a background thread
onCleared
. Jeśli chodzi o wątek tła - obserwuj z wątku głównego, to wszystko.observeForever
wywołanie z głównej viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
Użyj coroutines Kotlin z komponentami architektury.
Możesz użyć
liveData
funkcji konstruktora, aby wywołaćsuspend
funkcję, wyświetlając wynik jakoLiveData
obiekt.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 ustawieniaLiveData
wartoś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.0
lub nowszej.Jest też artykuł na ten temat.
Aktualizacja : możliwa jest również zmiana
LiveData<YourData>
wDao
interface
. Musisz dodaćsuspend
słowo kluczowe do funkcji:@Query("SELECT * FROM the_table") suspend fun getAll(): List<YourData>
iw tym
ViewModel
musisz uzyskać to asynchronicznie w ten sposób:viewModelScope.launch(Dispatchers.IO) { allData = dao.getAll() // It's also possible to sync other data here }
źródło