Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Jetpack Compose
  3. Gestion des états et des effets
Extrait - Jetpack Compose Développez des interfaces accessibles et modernes pour Android
Extraits du livre
Jetpack Compose Développez des interfaces accessibles et modernes pour Android Revenir à la page d'achat du livre

Gestion des états et des effets

Qu’est-ce qu’un état ?

Introduction

De manière générale, un état désigne toute valeur susceptible de changer au fil du temps. Que ce soit une variable dans une classe, une valeur stockée en base de données, ou encore une valeur fournie par une API tierce. Les écrans de nos applications Android sont, en fait, une représentation à un instant T des différents états qui la composent. Par exemple :

  • une liste de messages au sein d’une conversation

  • la position de la scroll bar dans cette liste de messages

  • un champ de saisie permettant d’effectuer une recherche sur ces messages

  • une animation qui se lance lorsque l’utilisateur clique sur le bouton permettant d’envoyer un nouveau message

  • etc.

Dans les chapitres précédents, nous avons créé des fonctions Composables dont les états pouvaient se situer à deux endroits différents :

  • Soit dans les paramètres. Par exemple, la fonction Composable MessageContent qui affiche le contenu d’un message.

@Composable 
fun MessageContent( 
    message: String, 
    time: String, 
    modifier: Modifier = Modifier 
) { 
    /*...*/ 
} 
  • Soit au sein de la fonction et persisté en mémoire grâce à la fonction remember{}. Par exemple, le composant NewMessageInput qui permet de saisir un nouveau message dans une conversation :

@Composable 
fun NewMessageInput( 
    modifier: Modifier = Modifier, 
    onSend: (String) -> Unit 
) {  
    var input by remember { mutableStateOf("") }  
    OutlinedTextField( 
        modifier...

Composant sans état interne

Lorsque nous concevons des interfaces avec Jetpack Compose, une bonne pratique consiste à limiter le nombre de composants avec un état interne et à développer au maximum des composants sans état. Dans cette section, nous allons essayer de comprendre pourquoi cette pratique est à privilégier et comment l’appliquer.

Vous entendrez souvent parler de composant Stateless pour parler de composant sans état et de composant Stateful pour parler de composant avec état.

Origine de cette pratique

Certains composants que nous concevons nécessitent l’accès à un état de type Screen UI State. Nous avons vu que ce type d’état est produit dans le State Holder de l’écran, typiquement le ViewModel. Imaginons un instant qu’au sein d’un même écran, chacun des composants accède directement au Screen UI State. Nous nous retrouvons avec de multiples connexions entre la vue et le State Holder, comme illustré ci-dessous. Dans cette situation, comme chaque composant a un cycle de vie unique, le risque de collision et de bogues lors de la manipulation du Screen UI est élevé.

images/07EI08.png

Illustration des multiples connexions

C’est pourquoi une bonne pratique consiste à accéder au Screen UI State uniquement depuis le composant à la racine de l’arbre de composition. Ce composant racine correspond généralement au rendu de l’écran complet. Cette pratique permet d’avoir un point d’entrée unique vers le State Holder de l’écran et d’appliquer ainsi le principe d’architecture de source unique de vérité (ou Single Source of Truth en anglais).

Appliquer cette bonne pratique se traduit par la conception de composants sans état interne, qui reçoivent...

Composants avec état interne

Nous allons maintenant découvrir comment un composant va pouvoir maintenir un état interne.

La fonction remember

De prime abord, pour développer un composant avec un état interne, il est tentant de simplement y instancier une variable de manière standard comme dans cet exemple :

@Composable 
fun NewMessage() { 
    var input: String = "" 
    TextField( 
        value = input, 
        onValueChange = { 
            input = it 
        } 
    ) 
} 

Cependant, en faisant ainsi, la variable input sera réinitialisée à chaque recomposition du composant. Pour permettre à une variable d’être persistée au-delà du mécanisme de recomposition d’un composant, il faut utiliser la fonction remember{}.Cette fonction a les caractéristiques suivantes :

  • C’est une fonction Composable.

  • Lors de la première composition du composant où elle se trouve, cette fonction sauvegarde la valeur qu’elle contient au sein de l’arbre de composition.

  • À chaque recomposition, elle retourne la valeur stockée en mémoire.

  • Dès que la fonction Composable qui utilise cette fonction est retirée de la Composition, la valeur sauvegardée est également supprimée. Il en est de même lors d’un changement de configuration, par exemple le changement d’orientation de l’écran.

  • Elle peut contenir un objet mutable ou immutable.

Cela donne pour le composant NewMessage le code suivant :

@Composable 
fun NewMessage()...

Arbre de décision : où gérer l’état ?

De prime abord, il peut sembler complexe de savoir comment bien gérer les états d’une application Jetpack Compose. Cet arbre de décision permet de mieux appréhender les différents points à éclaircir pour savoir où gérer l’état d’une fonction Composable.

images/07EI19.png

Arbre de décision « Où gérer l’état d’une fonction Composable ? »

Gestion des effets de bord

Introduction

Nous avons vu dans le chapitre La genèse de Jetpack Compose que pour fonctionner de manière optimale, une fonction Composable ne doit pas générer d’effet de bord, dit side effect en anglais.

Pour rappel, un effet de bord désigne toute opération qui échappe au contrôle et à la portée d’une fonction. Cela désigne par exemple des opérations telles que :

  • lire ou écrire une variable globale

  • lire ou écrire dans une base de données

  • lire ou écrire dans un fichier

  • effectuer un appel réseau

Bien que ces opérations soient tout à fait normales au sein d’une application, réaliser celles-ci au sein d’une fonction Composable peut générer des comportements imprévisibles liés au cycle de vie particulier des fonctions Composables.

Pour rappel, une fonction Composable peut subir plusieurs recompositions, être exécutée en parallèle et dans un ordre non prédictible. C’est pour cela qu’idéalement une fonction Composable ne devrait pas contenir d’effet de bord.

Pourtant, il existe certaines situations pour lesquelles il est nécessaire de créer des fonctions avec un effet de bord. Typiquement, lorsqu’une action nécessite de s’exécuter à des moments clés du cycle de vie d’une fonction Composable, par exemple lors de la première apparition d’un composant ou encore à chaque fois que l’état interne d’un composant change.

Exemple d’effet de bord acceptable

Reprenons le composant en charge de représenter la liste des messages au sein d’une conversation. Pour ce type de composant, il est souhaitable pour l’utilisateur que dès son ouverture, la liste des messages soit déroulée...