Ich habe eine separate Klasse, in der ich Daten abrufe (insbesondere Firebase), und ich gebe normalerweise LiveData-Objekte von dieser zurück und aktualisiere sie asynchron. Jetzt möchte ich die zurückgegebenen Daten in einem ViewModel speichern, aber das Problem ist, dass ich das LiveData-Objekt beobachten muss, das von meiner Datenabrufklasse zurückgegeben wird, um diesen Wert zu erhalten. Die Observ-Methode erforderte ein LifecycleOwner-Objekt als ersten Parameter, aber das habe ich offensichtlich nicht in meinem ViewModel und ich weiß, dass ich keinen Verweis auf die Aktivität / das Fragment im ViewModel behalten soll. Was soll ich machen?
92
Antworten:
In diesem Blog-Beitrag des Google-Entwicklers Jose Alcérreca wird empfohlen, in diesem Fall eine Transformation zu verwenden (siehe Abschnitt "LiveData in Repositorys"), da ViewModel keinen Verweis auf
View
(Aktivität, Kontext usw.) enthalten sollte, da dies die Arbeit erschwert zu testen.quelle
In der ViewModel- Dokumentation
Eine andere Möglichkeit besteht darin, dass die Daten RxJava anstelle von LiveData implementieren. Dann haben sie nicht den Vorteil, dass sie den Lebenszyklus berücksichtigen.
In einem Google-Beispiel von todo-mvvm-live-kotlin wird in ViewModel ein Rückruf ohne LiveData verwendet.
Ich vermute, wenn Sie der gesamten Idee, Lebenszyklusware zu sein, entsprechen möchten, müssen wir den Beobachtungscode in Aktivität / Fragment verschieben. Andernfalls können wir in ViewModel Callback oder RxJava verwenden.
Ein weiterer Kompromiss besteht darin, MediatorLiveData (oder Transformationen) zu implementieren und in ViewModel zu beobachten (hier Ihre Logik einfügen). Beachten Sie, dass der MediatorLiveData-Beobachter nur dann ausgelöst wird (wie Transformationen), wenn er in Aktivität / Fragment beobachtet wird. Wir setzen eine leere Beobachtung in Aktivität / Fragment, wo die eigentliche Arbeit tatsächlich in ViewModel erledigt wird.
// 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: Ich habe ViewModels und LiveData: Patterns + AntiPatterns gelesen , die Transformationen vorgeschlagen haben. Ich denke nicht, dass es funktioniert, wenn die LiveData nicht beobachtet werden (was wahrscheinlich erfordert, dass es bei Aktivität / Fragment durchgeführt wird).
quelle
mLiveData.asFlow()
) oderobserveForever
.Ich denke, Sie können watchForever verwenden, für das die Benutzeroberfläche des Lebenszyklusbesitzers nicht erforderlich ist, und Sie können die Ergebnisse des Ansichtsmodells beobachten
quelle
Cannot invoke observeForever on a background thread
onCleared
. Was den Hintergrund-Thread betrifft - vom Haupt-Thread aus beobachten, das war's.observeForever
, dass Sie von main viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
Verwenden Sie Kotlin-Coroutinen mit Architekturkomponenten.
Mit der
liveData
Builder-Funktion können Sie einesuspend
Funktion aufrufen und das Ergebnis alsLiveData
Objekt bereitstellen.val user: LiveData<User> = liveData { val data = database.loadUser() // loadUser is a suspend function. emit(data) }
Sie können auch mehrere Werte aus dem Block ausgeben. Jeder
emit()
Aufruf unterbricht die Ausführung des Blocks, bis derLiveData
Wert im Hauptthread festgelegt ist.val user: LiveData<Result> = liveData { emit(Result.loading()) try { emit(Result.success(fetchUser())) } catch(ioException: Exception) { emit(Result.error(ioException)) } }
Verwenden Sie in Ihrer Gradle-Konfiguration
androidx.lifecycle:lifecycle-livedata-ktx:2.2.0
oder höher.Es gibt auch einen Artikel darüber.
Update : Es ist auch möglich,
LiveData<YourData>
in der zu ändernDao
interface
. Sie müssensuspend
der Funktion das Schlüsselwort hinzufügen :@Query("SELECT * FROM the_table") suspend fun getAll(): List<YourData>
und in der
ViewModel
müssen Sie es so asynchron bekommen:viewModelScope.launch(Dispatchers.IO) { allData = dao.getAll() // It's also possible to sync other data here }
quelle