Composants d'architecture Android LiveData

Nous avons déjà parcouru beaucoup de terrain dans notre série de composants d’architecture Android. Nous avons commencé par parler de l'idée de la nouvelle architecture et des composants clés présentés dans Google I / O. Dans le second article, nous avons commencé notre exploration approfondie des principaux composants du paquet, en examinant de plus près les Cycle de la vie et LiveModel Composants. Dans cet article, nous allons continuer à explorer les composants d’architecture, en analysant cette fois l’impressionnant Données en direct composant.

Je suppose que vous connaissez les concepts et les composants abordés dans les derniers tutoriels, comme Cycle de la vie, Cycle de vie, et LifecycleObserver. Si vous ne l'êtes pas, jetez un coup d'œil au premier message de cette série, où je discute de l'idée générale de la nouvelle architecture Android et de ses composants.. 

Nous allons continuer à construire sur l'exemple d'application que nous avons démarré dans la dernière partie de cette série. Vous pouvez le trouver dans le tutoriel GitHub repo.

1. Le composant LiveData

Données en direct est un détenteur de données. Il est capable d'être observé, il peut contenir n'importe quel type de données et, en plus, sensible au cycle de vie. En termes pratiques, Données en direct peut être configuré pour envoyer des mises à jour de données uniquement lorsque son observateur est actif. Grâce à sa conscience du cycle de vie, quand observé par un Cycle de vie la Données en direct composant enverra des mises à jour uniquement lorsque l'observateur Cycle de la vie est toujours actif, et il va supprimer la relation observée une fois que l'observateur Cycle de la vie est détruit.

le Données en direct Le composant présente de nombreuses caractéristiques intéressantes:

  • empêche les fuites de mémoire lorsque l'observateur est lié à un Cycle de la vie
  • empêche les accidents dus aux activités arrêtées 
  • les données sont toujours à jour
  • gère les changements de configuration en douceur
  • permet de partager des ressources
  • gère automatiquement le cycle de vie

2. observer Données en direct

UNE Données en direct composant envoie des mises à jour de données uniquement lorsque son observateur est "actif". Observé par un Cycle de vie, la Données en direct composante considère que l’observateur n’est actif que lorsque Cycle de la vie est sur les états COMMENCÉ ou A REPRIS, sinon, l'observateur sera considéré comme inactif. Pendant l'état inactif de l'observateur, Données en direct arrêtera le flux de mise à jour des données jusqu'à ce que son observateur redevienne actif. Si l'observateur est détruit, Données en direct va supprimer sa référence à l'observateur.

Pour obtenir ce comportement, Données en direct crée une relation étroite avec l'observateur Cycle de la vie quand observé par un Cycle de vie. Cette capacité permet d'éviter plus facilement les fuites de mémoire lors de l'observation Données en direct. Cependant, si le processus d’observation est appelé sans Cycle de vie, la Données en direct le composant ne réagira pas Cycle de la vie état, et le statut de l'observateur doit être traité manuellement.

Observer un Données en direct, appel observer (LifecycleOwner, Observer) ou observerForever (observateur)

  • observer (LifecycleOwner, Observer): C’est le moyen standard d’observer un Données en direct. Il relie l'observateur à un Cycle de la vie, en changeant Données en directEtat actif et inactif selon le Cycle de vie état actuel.
  • observerForever (observateur): Cette méthode n'utilise pas de Cycle de vie, alors le Données en direct ne sera pas en mesure de répondre Cycle de la vie événements. Lorsque vous utilisez cette méthode, il est très important d’appeler removeObserver (Observateur), sinon l'observateur ne peut pas être ramassé, ce qui provoque une fuite de mémoire.
// appelé depuis un LifecycleOwner location.observe (// LifecycleOwner this, // création d'un observateur Observateur emplacement -> info ("emplacement: $ emplacement !!. latitude, $ emplacement.longitude") // Observer sans LifecycleOwner val observer = Observateur emplacement -> info ("emplacement: $ emplacement !!. Latitude, $ emplacement.longitude")) location.observeForever (observateur) // lorsque observateur sans LivecyleOwner // il est nécessaire de supprimer les observateurs à un moment donné location.removeObserver (observateur)

3. Mise en œuvre Données en direct

Le type générique dans le Données en direct La classe définit le type de données qui seront conservées. Par exemple, Données en direct retient Emplacement Les données. Ou Données en direct détient un Chaîne

Deux méthodes principales doivent être prises en compte dans la mise en œuvre du composant: onActive () et onInactive (). Les deux méthodes réagissent à l'état de l'observateur.

Exemple d'implémentation

Dans notre exemple de projet, nous avons utilisé beaucoup de Données en direct objets, mais nous n'en avons implémenté qu'un: LocationLiveData. La classe traite du GPS Emplacement, passer la position actuelle uniquement pour un observateur actif. Notez que la classe met à jour sa valeur sur le onLocationChanged méthode, en transmettant à l’observateur actif actuel le rapport actualisé Emplacement Les données.

Classe du constructeur LocationLiveData @Inject (contexte: contexte): LiveData(), LocationListener, AnkoLogger private value locationManager: LocationManager = context.getSystemService (Context.LOCATION_SERVICE) comme LocationManager @SuppressLint ("MissingPermission") annule le plaisir onInactive () info ("onInactive") locationManager.removeUpdates (this)) SuppressLint ("MissingPermission") fun refreshLocation () info ("refreshLocation") locationManager.requestSingleUpdate (LocationManager.GPS_PROVIDER, this, null)

4. Le MutableLiveData Classe d'assistance

le MutableLiveData est une classe d'assistance qui s'étend Données en direct, et expose postValue et setValue méthodes. Autre que cela, il se comporte exactement comme son parent. Pour l'utiliser, définissez le type de données qu'il contient, comme MutableLiveData tenir un Chaîne, et créer une nouvelle instance.

val myData: MutableLiveData = MutableLiveData ()

Pour envoyer des mises à jour à un observateur, appelez postValue ou setValue. Le comportement de ces méthodes est assez similaire; toutefois, setValue définira directement une nouvelle valeur et ne peut être appelée qu'à partir du thread principal, tandis que postValue crée une nouvelle tâche sur le thread principal pour définir la nouvelle valeur et peut être appelée à partir d'un thread d'arrière-plan.

fun updateData () // doit être appelé à partir du fil principal myData.value = api.getUpdate fun updateDataFromBG () // peut être appelé à partir du fil bg myData.postValue (api.getUpdate)

Il est important de considérer que, puisque le postValue méthode crée un nouveau Tâche et les messages sur le fil principal, il sera plus lent que les appels directs à setValue.

5. Transformations de Données en direct

Finalement, vous devrez changer un Données en direct et propager sa nouvelle valeur à son observateur. Ou peut-être devez-vous créer une réaction en chaîne entre deux Données en direct objets, en faisant réagir les changements sur un autre. Pour faire face aux deux situations, vous pouvez utiliser le Transformations classe.

Transformations de la carte

Transformations.map applique une fonction sur un Données en direct instance et envoie le résultat à ses observateurs, ce qui vous permet de manipuler la valeur des données..

Il est très facile à mettre en œuvre Transformations.map. Tout ce que vous avez à faire est de fournir un Données en direct à observer et un Une fonction être appelé quand l'observé Données en direct change, en se rappelant que le Une fonction doit renvoyer la nouvelle valeur du transformé Données en direct.

Supposons que vous ayez un Données en direct qui doit appeler une API quand un Chaîne la valeur, comme un champ de recherche, change.

// LiveData qui appelle api // quand 'searchLive' change de valeur val apiLive: LiveData = Transformations.map (searchLive, requête -> return @ map api.call (requête)) // Chaque fois que "searchLive" a // sa valeur mise à jour, il appellera // "apiLive" Transformation.map fun updateSearch (query: String) searchLive.postValue (query)

Transformations SwitchMap

le Transformations.switchMap est assez similaire à Transformations.map, mais il faut retourner un Données en direct objet à la suite. C'est un peu plus difficile à utiliser, mais cela vous permet de construire de puissantes réactions en chaîne. 

Dans notre projet, nous avons utilisé Transformations.switchMap créer une réaction entre LocationLiveData et ApiResponse

  1. Notre Transformation.switchMap observe LocationLiveData changements.
  2. le LocationLiveData la valeur mise à jour est utilisée pour appeler le MainRepository pour obtenir la météo pour l'emplacement spécifié.
  3. Le référentiel appelle le OpenWeatherService qui produit un Données en direct> Par conséquent.
  4. Ensuite, le retour Données en direct est observé par un MediatorLiveData, responsable de la modification de la valeur reçue et de la mise à jour de la météo présentée dans la couche de visualisation.
classe constructeur MainViewModel @Inject (référentiel de valeurs privé: MainRepository): ViewModel (), AnkoLogger // Emplacement emplacement de la valeur privée: LocationLiveData = repository.locationLiveDa () variable privée weatherByLocationResponse: LiveData> = Transformations.switchMap (emplacement, l -> info ("weatherByLocation: \ nlocation: $ l") return @ switchMap repository.getWeatherByLocation (l))

Méfiez-vous des opérations fastidieuses dans votre Données en direct transformations, cependant. Dans le code ci-dessus, les deux Transformations les méthodes s'exécutent sur le thread principal.

6. Le MediatorLiveData

MediatorLiveData est un type plus avancé de Données en direct. Ses capacités sont très similaires à celles du Transformations classe: il est capable de réagir à d'autres Données en direct objets, appelant un Une fonction lorsque les données observées changent. Cependant, il présente de nombreux avantages par rapport à Transformations, car il n'a pas besoin de fonctionner sur le fil principal et peut observer plusieurs Données en directs à la fois.

Observer un Données en direct, appel addSource (LiveData, Observateur), faire réagir l'observateur à la onChanged méthode de la donnée Données en direct. Pour arrêter l'observation, appelez removeSource (LiveData).

val mediatorData: MediatorLiveData = MediatorLiveData () mediatorData.addSource (dataA, valeur -> // réagit à la valeur info ("ma valeur $ valeur")) mediatorData.addSource (dataB, valeur -> // réagit à la valeur info ("ma valeur $ value ") // on peut supprimer le source une fois utilisé mediatorData.removeSource (dataB))

Dans notre projet, les données observées par la couche de vue contenant la météo à afficher sont un MediatorLiveData. Le composant observe deux autres Données en direct objets: weatherByLocationResponse, qui reçoit les mises à jour météorologiques par emplacement, et weatherByCityResponse, qui reçoit les mises à jour météorologiques par nom de ville. Chaque fois que ces objets sont mis à jour, weatherByCityResponse mettra à jour la couche de vue avec la météo actuelle demandée.

dans le MainViewModel, on observe le Données en direct et fournir le Météo objet à voir.

Classe constructeur MainViewModel @Inject (référentiel de valeurs privé: MainRepository): ViewModel (), AnkoLogger //… // Valeur observée par View. // Il transforme une réponse météo en un WeatherMain. météo privé: MediatorLiveData> = MediatorLiveData () // récupère la météo LiveData amusante getWeather (): LiveData> info ("getWeather") météo retour fun privé addWeatherSources () info ("addWeatherSources") weather.addSource (weatherByCityResponse, w -> info ("addWeatherSources: \ nweather: $ w !!.. data !!) !!  ") updateWeather (w.data !!)) weather.addSource (weatherByLocationResponse, w -> info (" addWeatherSources: weatherByLocationResponse: \ n $ w !!. données !! ") updateWeather (w.data! !)) private fun updateWeather (w: WeatherResponse) info ("updateWeather") // Obtenir la météo à partir de aujourd'hui val weatherMain = WeatherMain.factory (w) // Enregistrer sur les préférences partagées repository.saveWeatherMainOnPrefs (weatherMain) // update valeur météo weather.postValue (ApiResponse (data = weatherMain)) init //… addWeatherSources ()

dans le Activité principale, la météo est observée et son résultat est montré à l'utilisateur.

 private fun initModel () // Obtenez ViewModel viewModel = ViewModelProviders.of (this, viewModelFactory) .get (MainViewModel :: class.java) if (viewModel! = null) // observez la météo viewModel !!. getWeather (). observer (ceci @ MainActivity, Observateur r -> if (r! = null) info ("Météo reçue sur MainActivity: \ n $ r") if (! r.hasError ()) // N'en a pas errors info ("météo: $ r.data") if (r.data! = null) setUI (r.data) else // erreur ("erreur: $ r.error") isLoading ( false) if (r.error !!. statusCode! = 0) if (r.error !!. message! = null) toast (r.error.message !!) else toast ("Une erreur s'est produite") )

le MediatorLiveData a également été utilisé comme base d’un objet gérant les appels à l’API OpenWeatherMap. Jetez un coup d'œil à cette implémentation; c'est plus avancé que ceux ci-dessus, et ça vaut vraiment la peine d'étudier. Si vous êtes intéressé, jetez un oeil à OpenWeatherService, en accordant une attention particulière à la Médiateur classe.

7. Conclusion

Nous sommes presque à la fin de notre exploration des composants d'architecture d'Android. À présent, vous devriez comprendre suffisamment pour créer des applications puissantes. Dans le prochain post, nous allons explorer Pièce, un ORM qui enveloppe SQLite et peut produire Données en direct résultats. le Pièce composant s'inscrit parfaitement dans cette architecture, et il est la dernière pièce du puzzle.

À bientôt! Et en attendant, découvrez quelques-uns de nos autres articles sur le développement d'applications Android.!