Android

MVVM traditionnel avec Jetpack Compose et StateFlow | de Anastasia Finogenova | janvier 2022

Le 25 janvier 2022 - 7 minutes de lecture

Avec l’introduction de Jetpack Compose, certains développeurs peuvent supposer que son utilisation nécessite une approche architecturale différente de la manière dont vous créez une application Android. Les gens commencent à mentionner le mystérieux UDF 🕵️‍♀️. Mon objectif aujourd’hui est de montrer comment vous pouvez adapter les principes de base de MVVM que nous utilisons depuis des années, mais avec l’interface utilisateur Compose.

Avertissement : MVVM est UDF ou du moins il devrait être implémenté de cette façon 😉.

À des fins de récapitulation et pour nous assurer que nous sommes sur la même page de ce qu’est MVVM (basé sur mon expérience, c’est quelque chose que nous devons clarifier avant d’entrer dans une conversation 😅), voici un diagramme rapide que j’ai assemblé qui reflète également comment mon la mini application fonctionne.

Comme vous pouvez le voir, il n’y a pas de révélations ici. Juste de bonnes vieilles choses de base que vous avez peut-être déjà vues plusieurs fois en parlant de MVVM et en le représentant.

Nous avons la couche Modèle contenant la source de données accessible via un référentiel. Il y a le ViewModel contenant State et toute la logique agissant comme médiateur entre la source de données et View. La principale différence réside dans la couche View où résidera notre code Jetpack Compose.

Juste une note sur MVVM en général – lorsque vous utilisez ce modèle, peu importe avec Compose ou non, vous devez vous assurer que vous disposez d’un flux de données unidirectionnel (oui ! UDF). Cela signifie essentiellement que View ne pousse pas ou ne modifie pas l’état dans ViewModel. ViewModel fournira un état en réponse aux événements provenant de View (par exemple, des clics ou une entrée utilisateur).

Pour plus d’informations sur mon approche MVVM recommandée, consultez mon autre article.

Pour la partie StateFlow, nous avons besoin d’un moyen d’observer l’état et de notifier notre vue des changements. Il existe plusieurs façons de le faire et il y a définitivement une évolution des bibliothèques et des API largement utilisées. Parmi les plus récents, nous pouvons considérer LiveData, mais le fait est que Compose a été conçu dans Kotlin pour Kotlin et qu’il existe un support de première classe pour Flow apis. Donc, utiliser LiveData est possible, mais c’est presque comme si nous allions sortir du chemin proposé, ce qui rend les choses plus difficiles et il n’y a généralement aucune récompense à le faire dans ce cas. C’est donc Flow !

Nous n’avons aucun changement dans le référentiel ou la couche réseau lors de l’utilisation de Compose ou de Flow. Consultez le référentiel GitHub pour les détails de mise en œuvre.

Dans la couche ViewModel, nous introduirons l’état de l’interface utilisateur qui sera une classe scellée de tous les états de vue possibles pour alerter la vue des changements d’état et pour prendre en charge le concept de ViewModel étant la source de vérité pour l’état de notre interface utilisateur.

Comme vous pouvez le constater, nous avons répertorié tous les états de l’interface utilisateur que nous attendons en tant que classe scellée. Cela facilitera notre gestion de la syntaxe et de l’état à l’avenir.

Cet état va être publié via StateFlow pour que la vue collecte et soit notifiée des modifications. Nous aurons un MutableStateFlow dans ViewModel et un champ de sauvegarde pour encapsuler la mutabilité de ce producteur dans la classe ViewModel.

À l’étape suivante, nous pouvons implémenter la logique d’interrogation des données du référentiel et de mise à jour de l’état de l’interface utilisateur.

Quelques choses se passent ici. Détaillons ligne par ligne.

Nous commençons par déclencher l’état en cours de chargement pour afficher notre interface utilisateur de progression.

_uiState.value = WeatherUiState.Loading

Ensuite, nous invoquons notre fun de suspension à partir du référentiel qui renvoie un objet de réponse. Nous pouvons maintenant l’analyser et définir l’état de l’interface utilisateur sur Chargé pour afficher les données de manière appropriée.

Si nous rencontrons une exception, nous allons également la définir dans le flux d’état de l’interface utilisateur comme celui-ci, par exemple.

_uiState.value = WeatherUiState.Error(              applicationContext.getString(R.string.query_limit_reached)        
)

Du côté des consommateurs, nous avons View qui est implémenté avec Jetpack Compose.

Comme il ne s’agit pas d’un didacticiel Compose, je n’aborderai que brièvement l’interface utilisateur elle-même. L’application météo est une application d’une page, nous pouvons donc simplement utiliser MainActivity et y rendre nos composables.

Nous allons collecter la valeur de notre producteur de flux directement dans la fonction composable qui représente essentiellement la vue pleine page.

mainViewModel.uiState.collectAsState().value

Selon l’état de l’interface utilisateur que nous recevons, nous pouvons charger différents composables dans notre cadre principal.

Notez que nous acceptons les données dans le constructeur du composable d’écran chargé, ce qui nous permet, premièrement, de maintenir la testabilité car vous pouvez toujours insérer des données fictives dans le constructeur et, deuxièmement, d’éviter la logique à l’intérieur du composable d’écran chargé pour le garder maigre et responsable uniquement de rendu des composants pour représenter les données dont nous disposons.

@Composable
fun WeatherLoadedScreen(data: WeatherUiModel)

*La classe complète se trouve dans le référentiel GitHub.

Nous avons maintenant toutes nos couches MVVM traditionnelles câblées à l’aide de Compose et StateFlow. L’exemple d’application est assez léger et basique, mais c’est fait exprès pour éliminer le bruit et mettre en valeur l’architecture. L’architecture et l’approche ne changent pas beaucoup.

  • Conservez toute la logique dans ViewModel. Si vous pouvez mettre du code Kotlin contenant de la logique dans des composables, cela ne signifie pas que vous devriez le faire.
  • Ne placez aucune interaction source de données/réseau dans les composables. Fournissez des données de manière unidirectionnelle de ViewModel à View.
  • Fournissez toutes les dépendances dans les composables de l’extérieur ainsi que l’état pour les garder légers et permettre les simulations et les échanges de données lors des tests.
  • Utilisez ViewModel comme source de State pour garder les choses cohérentes et pour les mêmes raisons qu’auparavant – pour conserver l’état.
  • Décomposez les composables en moins de composants pour éviter les fonctions de graisse folles. Si votre linter vous crie dessus avec des fonctions à 28 lignes, les composables sont pareils.
  • Gardez à l’esprit que les composables peuvent se recomposer plusieurs fois et que les calculs lourds qu’ils contiennent affecteront les performances.

J’espère que vous avez apprécié la lecture. Ma principale préoccupation avec l’adoption de Compose a toujours été le modèle de classe “Dieu”. J’avais l’impression qu’ils rendaient trop facile le retour à “Dieu” ~Activités~, enfin maintenant – Composables. C’est donc à nous de maintenir toutes les choses positives que nous avons obtenues avec le code en termes de code propre et de testabilité avec l’adoption de Compose.

Projet GitHub :

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.