Observation des données en direct à partir de ViewModel

91

J'ai une classe distincte dans laquelle je gère la récupération de données (en particulier Firebase) et j'en renvoie généralement des objets LiveData et je les mets à jour de manière asynchrone. Maintenant, je veux que les données retournées soient stockées dans un ViewModel, mais le problème est que pour obtenir cette valeur, je dois observer l'objet LiveData renvoyé par ma classe de récupération de données. La méthode observe nécessitait un objet LifecycleOwner comme premier paramètre, mais je n'ai évidemment pas cela à l'intérieur de mon ViewModel et je sais que je ne suis pas censé garder une référence à l'activité / fragment à l'intérieur du ViewModel. Que devrais-je faire?

Vuk Bibic
la source

Réponses:

38

Dans ce billet de blog du développeur Google Jose Alcérreca, il est recommandé d'utiliser une transformation dans ce cas (voir le paragraphe "LiveData dans les référentiels") car ViewModel ne doit contenir aucune référence liée à View(Activité, Contexte, etc.) car cela a rendu les choses difficiles tester.

guglhupf
la source
avez-vous réussi à faire fonctionner Transformation pour vous? Mes événements ne fonctionnent pas
romaneso
23
Les transformations en elles-mêmes ne fonctionnent pas, car le code que vous écrivez dans la transformation n'est attaché pour s'exécuter que lorsqu'une entité observe la transformation .
orbitbot
5
Je ne sais pas pourquoi c'est la réponse recommandée, cela n'a rien à voir avec la question. 2 ans plus tard, et nous ne savons toujours pas comment observer les changements de données du référentiel dans notre viewmodel.
Andrew le
24

Dans la documentation de ViewModel

Cependant, les objets ViewModel ne doivent jamais observer les modifications des observables sensibles au cycle de vie, tels que les objets LiveData.

Une autre façon est que les données implémentent RxJava plutôt que LiveData, alors elles n'auront pas l'avantage d'être conscient du cycle de vie.

Dans google sample de todo-mvvm-live-kotlin , il utilise un rappel sans LiveData dans ViewModel.

Je suppose que si vous voulez vous conformer à l'idée générale d'être un élément du cycle de vie, nous devons déplacer le code d'observation dans Activity / Fragment. Sinon, nous pouvons utiliser le rappel ou RxJava dans ViewModel.

Un autre compromis consiste à implémenter MediatorLiveData (ou Transformations) et à observer (mettez votre logique ici) dans ViewModel. Remarquez que l'observateur MediatorLiveData ne se déclenchera pas (comme les transformations) sauf s'il est observé dans Activity / Fragment. Ce que nous faisons est de mettre une observation vide dans Activity / Fragment, où le vrai travail est réellement effectué dans 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: j'ai lu ViewModels et LiveData: Patterns + AntiPatterns qui suggéraient que Transformations. Je ne pense pas que cela fonctionne à moins que les LiveData ne soient observés (ce qui nécessite probablement que cela soit fait à Activity / Fragment).

Desmond Lua
la source
2
Quelque chose a-t-il changé à cet égard? Ou RX, rappel ou observation à blanc ne sont que des solutions?
qbait
2
Une solution pour se débarrasser de ces observations vierges?
Ehsan Mashhadi
1
Peut-être en utilisant Flow ( mLiveData.asFlow()) ou observeForever.
Machado le
La solution de flux semble fonctionner si vous ne voulez pas / vous n'avez pas besoin de logique d'observateur dans Fragment
adek111
14

Je pense que vous pouvez utiliser observeForever qui ne nécessite pas l'interface du propriétaire du cycle de vie et vous pouvez observer les résultats à partir du modèle de vue

siddharth
la source
2
cela me semble la bonne réponse en particulier que dans la documentation sur ViewModel.onCleared () est dit: "C'est utile lorsque ViewModel observe certaines données et que vous devez effacer cet abonnement pour éviter une fuite de ce ViewModel."
Yosef
2
Désolé maisCannot invoke observeForever on a background thread
Boken
1
Cela semble tout à fait légitime. Cependant, il faut enregistrer les observateurs dans les champs viewModel et se désabonner à onCleared. En ce qui concerne le fil d'arrière-plan - observez à partir du fil principal, c'est tout.
Kirill Starostin
@Boken Vous pouvez forcer observeForeverà être invoqué depuis la principale viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle
4

Utilisez des coroutines Kotlin avec des composants d'architecture.

Vous pouvez utiliser la liveDatafonction de générateur pour appeler une suspendfonction, servant le résultat comme un LiveDataobjet.

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

Vous pouvez également émettre plusieurs valeurs à partir du bloc. Chaque emit()appel suspend l'exécution du bloc jusqu'à ce que la LiveDatavaleur soit définie sur le thread principal.

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

Dans votre configuration gradle, utilisez androidx.lifecycle:lifecycle-livedata-ktx:2.2.0ou supérieur.

Il y a aussi un article à ce sujet.

Mise à jour : il est également possible de modifier LiveData<YourData>le fichier Dao interface. Vous devez ajouter le suspendmot - clé à la fonction:

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

et dans le ViewModelvous devez l'obtenir de manière asynchrone comme ça:

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