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 :
Une chaîne de caractères
title
qui permet de personnaliser le texte du composant.Un paramètre
status
, 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.
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.
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.
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.
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 composantText
contenant 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 !
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 composantIconButton
fourni 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ètreonClick
de 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 composantPodcastItem
et que nous souhaitons garantir sa réutilisabilité, nous allons passer cette action de clic comme paramètre dePodcastItem
comme présenté ci-dessous.
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.
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.
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
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 de
PodcastItem
. 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 ?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”.
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 !