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?
91
Réponses:
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.la source
Dans la documentation de ViewModel
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).
la source
mLiveData.asFlow()
) ouobserveForever
.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
la source
Cannot invoke observeForever on a background thread
onCleared
. En ce qui concerne le fil d'arrière-plan - observez à partir du fil principal, c'est tout.observeForever
à être invoqué depuis la principale viaGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
Utilisez des coroutines Kotlin avec des composants d'architecture.
Vous pouvez utiliser la
liveData
fonction de générateur pour appeler unesuspend
fonction, servant le résultat comme unLiveData
objet.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 laLiveData
valeur 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.0
ou supérieur.Il y a aussi un article à ce sujet.
Mise à jour : il est également possible de modifier
LiveData<YourData>
le fichierDao
interface
. Vous devez ajouter lesuspend
mot - clé à la fonction:@Query("SELECT * FROM the_table") suspend fun getAll(): List<YourData>
et dans le
ViewModel
vous 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 }
la source