LiveData von ViewModel aus beobachten

92

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?

Vuk Bibic
quelle

Antworten:

38

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.

guglhupf
quelle
Haben Sie es geschafft, Transformation für Sie zum Laufen zu bringen? Meine Veranstaltungen funktionieren nicht
Romaneso
24
Transformationen alleine funktionieren nicht, da der Code, den Sie in die Transformation schreiben, nur zur Ausführung angehängt wird, wenn eine Entität die Transformation beobachtet .
Orbitbot
8
Ich weiß nicht, warum dies die empfohlene Antwort ist, sie hat nichts mit der Frage zu tun. 2 Jahre später wissen wir immer noch nicht, wie wir Änderungen der Repository-Daten in unserem Ansichtsmodell beobachten können.
Andrew
25

In der ViewModel- Dokumentation

ViewModel-Objekte dürfen jedoch niemals Änderungen an lebenszyklusbewussten Observablen wie LiveData-Objekten beobachten.

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).

Desmond Lua
quelle
2
Hat sich diesbezüglich etwas geändert? Oder RX, Callback oder Blank Observ sind nur Lösungen?
Qbait
2
Gibt es eine Lösung, um diese leeren Beobachtungen loszuwerden?
Ehsan Mashhadi
1
Vielleicht mit Flow ( mLiveData.asFlow()) oder observeForever.
Machado
Die Flow-Lösung scheint zu funktionieren, wenn Sie in Fragment
adek111
14

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

Siddharth
quelle
2
Dies scheint mir die richtige Antwort zu sein, insbesondere, dass in den Dokumenten zu ViewModel.onCleared () Folgendes steht: "Es ist nützlich, wenn ViewModel einige Daten beobachtet und Sie dieses Abonnement löschen müssen, um ein Durchsickern dieses ViewModel zu verhindern."
Josef
2
Sorry aberCannot invoke observeForever on a background thread
Boken
1
Das scheint ziemlich legitim zu sein. Man muss jedoch Beobachter in den viewModel-Feldern speichern und sich abmelden onCleared. Was den Hintergrund-Thread betrifft - vom Haupt-Thread aus beobachten, das war's.
Kirill Starostin
@Boken Sie können erzwingen observeForever, dass Sie von main viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle
5

Verwenden Sie Kotlin-Coroutinen mit Architekturkomponenten.

Mit der liveDataBuilder-Funktion können Sie eine suspendFunktion aufrufen und das Ergebnis als LiveDataObjekt 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 der LiveDataWert 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.0oder höher.

Es gibt auch einen Artikel darüber.

Update : Es ist auch möglich, LiveData<YourData>in der zu ändern Dao interface. Sie müssen suspendder Funktion das Schlüsselwort hinzufügen :

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

und in der ViewModelmüssen Sie es so asynchron bekommen:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}
Psijic
quelle