• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 01/10/2024

Explorez la recomposition et la gestion d’état

Il est essentiel pour comprendre pleinement le fonctionnement et les avantages de Jetpack Compose de prendre le temps d’explorer les capacités si particulières que sont les fonctions composables.

Explorez les capacités des fonctions composables

Continuons notre découverte des spécificités de Jetpack Compose en analysant les spécificités du code du composantPodcastItem.

@Composable
fun PodcastItem(
   title: String,
   status: DownloadStatus,
) {
   Row {
       Text(
           text = title,
       )
       when (status) {
           DownloadStatus.Online ->
               Icon(
                   imageVector = Icons.Default.Cloud,
                   contentDescription = "Disponible sur le réseau",
               )
           DownloadStatus.InProgress ->
               Icon(
                   imageVector = Icons.Default.Downloading,
                   contentDescription = "Téléchargement en cours",
               )
           DownloadStatus.Downloaded ->
               Icon(
                   imageVector = Icons.Default.DownloadDone,
                   contentDescription = "Téléchargé",
               )
       }
   }
}

Focalisons notre attention sur la déclaration du composant et non son contenu.

@Composable
fun PodcastItem(
   title: String,
   status: DownloadStatus,
) {
/*...*/
}

Nous avons précédemment constaté qu’un composant en Jetpack Compose s’écrit sous forme de fonction. Si on analyse un peu plus en profondeur les spécificités de cette déclaration, nous observons les choses suivantes :

  • Cette fonction prend en entrée des paramètres et ne retourne rien.

  • La présence de l’annotation@Composable.

Essayons de comprendre point par point ces spécificités.

Une fonction composable prend en entrée des paramètres et ne retourne rien.

Dans notre composantPodcastItem, la fonction prend deux paramètres :

  1. Une chaîne de caractèrestitlequi permet de personnaliser le texte du composant.

  2. Un paramètrestatus, représentant l’état de téléchargement, qui permet de définir l’icône à afficher.

En prenant du recul sur cette déclaration si particulière, on peut se demander comment l’interface est produite, puisque cette fonction ne retourne rien. Ce qu’il faut comprendre, c’est que c’est lors de l’exécution de cette fonction que la magie opère et que Jetpack Compose va émettre l’interface utilisateur reflétant les valeurs de ces paramètres.

À ce stade, la notion d’émettre l’interface peut vous paraître abstraite. C’est normal, ça l’était pour moi aussi. Sachez que tout se passe à la compilation. En effet, le code compilé de votre fonction contient, en plus de votre code, des instructions chargées d’écrire en mémoire la description de l’interface. Ensuite, lors de l'exécution des instructions, cette description est lue par le système, qui peut ainsi dessiner l’interface à l’écran.

Le schéma montre une fonction marquée par l'annotation @Composable. La fonction prend des données en entrée et produit une description de l'interface utilisateur en sortie, symbolisant la génération d'UI à partir de données.

La présence de l’annotation@Composable.

Passons maintenant à la seconde particularité de la déclaration d’un composant. La fonction qui le représente est annotée avec@Composable. On parle aussi de fonction composable. Elle représente un nouveau type de fonction. Ce type de fonction constitue le bloc fondamental pour créer une interface avec Jetpack Compose. C’est grâce à cette annotation que la fonction peut générer des éléments d'interface lors de son exécution. Cette phase d’exécution est appelée composition.

Comprenez le mécanisme de recomposition

Reprenons la notion de composition qui signifie simplement qu’à un instant t, le code compilé de notre fonction est exécuté. Cette action va émettre la description de l’interface sous forme d’arbre.

L'image illustre le processus de compilation et d'exécution d'une fonction annotée avec @Composable en Kotlin, et son effet sur l'arbre de composition. Le code source compilé de la fonction Hello() est compilé à un instant t.
Mécanisme de composition

Mais que se passe-t-il alors quand les paramètres d’une fonction composable changent ? Comment l’interface va-t-elle se mettre à jour ?

Il va y avoir une recomposition. En fait, la fonction composable va se réexécuter afin que l’interface reflète le nouvel état de notre application. 

La première composition

Reprenons le composantPodcastItem. Imaginons que le titre du podcast soit initialement “Interview de Jean-Marc Jancovici” et son statut de téléchargement soitInProgress

La fonctionPodcastItem(title = "Interview de Jean-Marc Jancovici", status = DownloadStatus.InProgress)est donc exécutée une première fois. L’interface correspondante est émise. Durant cette exécution, les valeurstitle = "Interview de Jean-Marc Jancovici"’ et ‘status = InProgress  produisent une ligne (row en anglais) avec un texte et une icône de progression.

C’est la première composition du composant.

L'image montre la compilation et l'exécution de la fonction composable PodcastItem en Jetpack Compose. La fonction prend deux paramètres : title (chaîne) et status (DownloadStatus). À l'exécution, les valeurs title = Interview de Jean-Marc Jancovici
Première composition du composant ‘PodcastItem’

La recomposition

Quelques secondes plus tard, alors que l’utilisateur est toujours sur le même écran, le statut passe àDownloaded. Jetpack Compose est très intelligent et détecte que l’un des paramètres de la fonction a changé. Pour refléter les changements, tout seul comme un grand, il va réexécuter la fonction composable avec les nouveaux paramètres. Ici il s’agit du statut qui a changé. La description du composant en mémoire est alors mise à jour.

L'image illustre la recompilation de la fonction composable PodcastItem en Jetpack Compose à un instant t+1. La fonction prend deux paramètres : title (chaîne) et status (DownloadStatus). Lors de la recomposition, les valeurs sont title = Interview de
Recomposition du composant 'PodcastItem'

Les optimisations faites par Jetpack Compose

Sachez que pour optimiser ses performances, lorsque les paramètres d’une fonction composable changent, Jetpack Compose recompose uniquement les enfants de cette fonction qui sont impactés par ces changements.

Dans notre exemple, le titre n’a pas changé entre l’instant t et l’instant t+1. Le composantTextcontenant le titre ne sera alors pas recomposé. Ça permet d’économiser les ressources nécessaires à ce processus. Sans cela, Jetpack Compose ne serait pas une solution viable !

Schéma représentant l’arbre de composition du composant PodcastItem. On peut voir qu’il contient une Row, avec un composant texte qui a été composé une fois et une icône qui a été recomposée deux fois.
Illustration du nombre de recomposition

Découvrez les événements

Maintenant que vous avez compris comment est créé un composant qui reflète les données d’une application, il est temps de découvrir comment capter les événements d‘une interface.

Pour capturer un événement au sein d’un composant, nous allons utiliser une lambda dans les paramètres de la fonction composable de ce composant. 

Des lambdas pour gérer les événements

Reprenons par exemple le composantPodcastItem que nous avons vu au début de cette section. Imaginons que lorsque le status estOnline, l’icône soit maintenant un bouton icône cliquable permettant de télécharger le podcast lorsque l’utilisateur clique dessus. Pour ça, nous allons utiliser le composantIconButtonfourni par Jetpack Compose.

IconButton(
   onClick = { /*  TODO (plus tard il faudra mettre le code à exécuter ici) */},
) {
   Icon(
       imageVector = Icons.Default.Download,
       contentDescription = "Télécharger $title",
   )
}

Focalisons notre attention sur le paramètreonClickde la fonctionIconButton. En fait, il s’agit justement d’une lambda. À l'intérieur, nous pourrons démarrer le téléchargement. Nous devons intégrer ce bouton au sein de notre composantPodcastItem, comme ci-dessous.

@Composable
fun PodcastItem(
   title: String,
   status: DownloadStatus,
) {
   Row {
       Text(
           text = title,
       )
       when (status) {
DownloadStatus.Online ->
   IconButton(onClick = /* TODO */) {
       Icon(
           imageVector = Icons.Default.Download,
           contentDescription = "Télécharger $title",
       )
   }
           /*...*/
       }
         }
}

Comme l'action de clic est une fonctionnalité fondamentale du composantPodcastItemet que nous souhaitons garantir sa réutilisabilité, nous allons passer cette action de clic comme paramètre dePodcastItemcomme présenté ci-dessous. 

Schéma montrant une fonction Kotlin annotée avec @Composable. La fonction accepte deux paramètres : title de type String (données) et onDownloadClicked de type lambda (() -> Unit) (événements).
Les états et événements en paramètres d’une fonction composable

Cela se traduit par les modifications suivantes.

@Composable
fun PodcastItem(
   title: String,
   status: DownloadStatus,
   onDownloadClicked: () -> Unit,
) {
   Row {
       /*...*/
       IconButton(
           onClick = onDownloadClicked,
       ) {
           Icon(
               imageVector = Icons.Default.Download,
               contentDescription = "Télécharger $title",
           )
       }
   }
}

Transmettre une lambda (par exemple pour un clic) et les états en paramètres plutôt que de les gérer à l'intérieur de la fonction composable est une pratique fondamentale du développement de composants avec Jetpack Compose. Les composants qui suivent cette approche sont appelés des composants sans état, ou stateless components en anglais. Nous plongerons en détail dans cette pratique dans la quatrième partie du cours “Finalisez la structure de l’application”

Prévisualisez vos composants

Pour prévisualiser le rendu d’un composant au sein d’Android Studio, Jetpack Compose fournit l’annotation@Preview. Écrivons le code suivant. 

@Preview
@Composable
fun PreviewPodcastItem() {
   PodcastItem(
       title = "Interview de Jean-Marc Jancovici",
       status = DownloadStatus.InProgress,
       onDownloadClicked = {},
   )
}

En passant en vue “Split” ou en vue “Design”, cela donne cette prévisualisation.

Capture d'écran d'un composant PreviewPodcastItem affichant le titre Interview de Jean-Marc Jancovici avec deux icônes de téléchargement. La première icône indique un téléchargement en cours, représentée par une flèche entourée d'un cercle
Prévisualisation du composant ‘PodcastItem’ sur Android Studio

C’est magique ! Plus besoin de redémarrer l’application pour tester son rendu. Il est possible de générer autant de prévisualisations que l'on veut. Si vous êtes curieux, vous pouvez même cliquer sur la roue crantée à droite de l’annotation @Preview pour configurer les paramètres de l’appareil virtuel contenant votre prévisualisation (ses dimensions, son orientation ou encore la taille de la police définie sur l’appareil). C’est très utile pour tester l’accessibilité de son application.

La fenêtre comprend des paramètres tels que le nom et le groupe, ainsi que des sections pour le matériel et l'affichage. Les options matérielles incluent l'appareil (Default), les dimensions (1080 x 2340 px), la densité (440 dpi), l'orientation (port
Personnaliser les paramètres de prévisualisation

Bon, je suis d’accord que pour le moment, le composant manque un peu d’espace et d’alignement. Ne vous en faites pas, c’est ce que nous allons voir dans la prochaine partie de ce cours.

À vous de jouer !

Contexte

Il est encore trop tôt pour créer un écran mais il est important pour vous de bien comprendre les différents concepts que nous avons vus ici. Voici donc pour rappel le code du composantPodcastItem.

@Composable
fun PodcastItem(
   title: String,
   status: DownloadStatus,
   onDownloadClicked: () -> Unit,
) {
   Row {
       Text(
           text = title,
       )
       when (status) {
           DownloadStatus.Online ->
               IconButton(onClick = onDownloadClicked) {
                   Icon(
                       imageVector = Icons.Default.Download,
                       contentDescription = "Télécharger $title",
                   )
               }
           DownloadStatus.InProgress ->
               Icon(
                   imageVector = Icons.Default.Downloading,
                   contentDescription = "Téléchargement en cours",
               )
           DownloadStatus.Downloaded ->
               Icon(
                   imageVector = Icons.Default.DownloadDone,
                   contentDescription = "Téléchargé",
               )
       }
   }
}

Consignes

  1. Nous sommes à un instant t=0, au moment où l’utilisateur affiche l’écran qui permet de visualiser les détails d’un podcast. C’est une première composition dePodcastItem. Quelques minutes plus tard, le titre du podcast change parce qu’une modification a été effectuée dans la base de données. L’écran doit refléter ce changement d’état. Que va-t'il se passer durant la phase de recomposition ? 

  2.  Créez plusieurs prévisualisations :

    • Une prévisualisation avec le statut de téléchargement “Online”.

    • Une prévisualisation avec le statut de téléchargement “InProgress”.

    • Une prévisualisation avec le statut de téléchargement  “Downloaded”.

  3. Modifiez les paramètres par défaut de la prévisualisation et observez le rendu.

Livrables

Pour réaliser la première tâche, vous pouvez utiliser un format texte. La deuxième et troisième tâche se font directement sur Android Studio.

En résumé

  • Un composant Jetpack Compose est déclaré sous forme de fonction.

  • Une fonction composable prend en paramètres les états dont dépend le composant ainsi que les événements captés.

  • Une fonction composable ne retourne rien, mais émet une interface grâce à l’annotation@Composable.

  • Lors de la compilation, le compilateur détecte l'annotation  @Composable,  injecte des instructions pour émettre l’interface en mémoire, et à l'exécution, ces instructions sont lues par le système pour dessiner l’interface à l’écran.

  • La phase d'exécution d’une fonction composable s’appelle la composition. 

  • Lorsqu’un des paramètres d’une fonction composable change, alors Jetpack Compose déclenche une recomposition de celle-ci, afin de refléter les changements correspondants à l’écran.

Allez, il est grand temps de développer des interfaces qui en jettent ! Pour cela, rendez-vous dans la prochaine partie où nous apprendrons à utiliser la boîte à outils de Jetpack Compose. Nous verrons comment créer des textes, des images, des boutons, et comment les mettre en page de manière efficace. Préparez-vous à maîtriser les composants essentiels pour concevoir des interfaces attrayantes et fonctionnelles !

Exemple de certificat de réussite
Exemple de certificat de réussite