Maîtriser la recomposition
Des paramètres stables et moins de recompositions.
Maîtriser la recomposition est une leçon Android Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Android Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Android Academy comprend 4 leçons au total.
Recomposition : le levier de performance de Compose
Dans Jetpack Compose, l'interface utilisateur est décrite par des fonctions. Lorsque l'état change, Compose effectue une recomposition : il réexécute les composables qui lisent cet état afin de mettre à jour l'écran.
La recomposition est normale et peu coûteuse lorsqu'elle est bien limitée. Mais lorsqu'elle se produit trop souvent ou sur une portion trop importante de l'arbre, elle devient la première cause de saccades dans Compose. Cette leçon vous apprend à rendre la recomposition limitée et rare.
Afficher le nombre de recompositions
Avant de corriger, mesurez. L'inspecteur de mise en page d'Android Studio affiche en temps réel le nombre de recompositions de chaque composable. Vous pouvez également utiliser les métriques du compilateur Compose ou un simple compteur de débogage.
Une astuce simple : un SideEffect incrémente une référence chaque fois qu'un composable est recomposé, ce qui vous permet d'enregistrer les comportements surprenants pendant le développement.
@Composable
fun RecompositionCounter(tag: String) {
val count = remember { mutableStateOf(0) }
SideEffect { count.value++ }
Log.d("Recompose", "$tag recomposed ${count.value} times")
}
// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.Lire l'état le plus tard possible
Compose ne recompose que les composables qui lisent une valeur d'état. Si un parent lit l'état, le parent entier est recomposé ; si seul un petit enfant le lit, seul cet enfant est recomposé.
Faites donc descendre les lectures d'état vers le bas de l'arbre. Ici, la mauvaise version recompose toute la Column à chaque top ; la bonne version limite la recomposition à l'étiquette.
// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
Column {
ExpensiveHeader()
Text("Elapsed: $seconds")
}
}
// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
Column {
ExpensiveHeader()
Text("Elapsed: ${seconds()}")
}
}Différer les lectures avec des lambdas
Un modèle puissant consiste à transmettre, au lieu d'une valeur qui change souvent, une lambda qui la renvoie. Le composable qui appelle finalement la lambda est le seul à être recomposé.
C'est pourquoi Modifier.offset { ... } et graphicsLayer { ... } acceptent des lambdas : les valeurs de défilement et d'animation changent à chaque trame, et la lambda empêche entièrement la recomposition pendant la phase de mise en page.
// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))
// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })
// Same idea for alpha/scale during animation:
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)Stabilité : pourquoi Compose ignore des recompositions
Compose peut ignorer la recomposition d'un composable si tous ses paramètres sont stables et n'ont pas changé. Un type est stable lorsque Compose peut faire confiance à equals pour refléter les changements réels et lorsque ses champs publics ne sont pas modifiés à son insu.
- Stables : types primitifs,
String, classes de données immuables,State. - Instables : interfaces
List/Map, classes contenant des champsvar, types provenant de modules sans le compilateur.
Un paramètre instable force la recomposition même lorsque rien n'a changé.
// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }
data class User(val id: Long, val name: String) // stable: all valsRendre les paramètres stables
Voici deux corrections courantes pour les paramètres instables :
- Utilisez des collections immuables provenant de
kotlinx.collections.immutable(par exempleImmutableList), que Compose considère comme stables. - Annotez une classe que vous contrôlez avec
@Immutableou@Stablepour garantir à Compose qu'elle ne changera pas.
Compose peut alors ignorer sans risque la recomposition lorsque la même instance est transmise à nouveau.
import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable
@Immutable
data class UiState(
val title: String,
val users: ImmutableList<User>
)
// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }remember : ne recalculez pas à chaque trame
Les composables peuvent s'exécuter de nombreuses fois. Tout calcul non trivial effectué directement dans le corps est exécuté à chaque recomposition. Placez-le dans remember afin qu'il ne soit recalculé que lorsque ses clés changent.
Pour les valeurs dérivées d'autres états, préférez derivedStateOf, qui ne réémet une valeur que lorsque le résultat calculé change réellement.
// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }
// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()Clés stables dans les listes différées
Dans LazyColumn/LazyRow, attribuez une clé stable à chaque élément. Sans clés, l'insertion ou le réordonnancement d'éléments force Compose à recomposer et à mesurer à nouveau des éléments qui n'ont en réalité pas changé, car il les suit par leur position.
Avec une clé stable, Compose peut associer les éléments entre les mises à jour et ignorer ceux qui n'ont pas changé.
LazyColumn {
items(
items = users,
key = { user -> user.id } // stable identity
) { user ->
UserRow(user)
}
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.Remonter l'état, transmettre les événements vers le haut
Des paramètres lambda instables peuvent également empêcher l'ignorance d'une recomposition si une nouvelle instance de lambda est créée à chaque recomposition. Stabilisez les rappels en les mémorisant ou en référençant des fonctions stables.
Associée à la remontée de l'état (l'état vit dans l'appelant et les événements remontent), cette approche permet aux composables feuilles de rester peu coûteux et ignorables.
@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
TextField(value = query, onValueChange = onQueryChange)
}
// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)Éviter de lire l'état du défilement ou de l'animation trop haut dans l'arbre
Une erreur classique consiste à lire un état qui change rapidement (décalage du défilement, progression de l'animation) dans un composable de niveau élevé. Cela force tout le sous-arbre à être recomposé à chaque trame.
Gardez ces lectures dans les lambdas de Modifier (offset {}, graphicsLayer {}, drawBehind {}) afin que le travail s'effectue pendant la phase de mise en page ou de dessin, et non pendant la recomposition. C'est ce qui fait la différence entre un défilement fluide à 60 images par seconde et un défilement saccadé.
// Animated color used only for drawing -> stay in the draw phase
Box(
Modifier.drawBehind {
drawRect(color = animatedColor()) // lambda read, no recomposition
}
)Liste de contrôle de la recomposition
Lorsqu'un écran semble saccadé pendant une interaction, parcourez cette liste :
- Les paramètres sont-ils stables (données immuables,
ImmutableList) ? - Lisez-vous l'état qui change rapidement aussi bas que possible, idéalement dans des lambdas de modificateur ?
- Les listes différées possèdent-elles des clés stables ?
- Les calculs coûteux sont-ils placés derrière
remember/derivedStateOf? - L'inspecteur de mise en page a-t-il confirmé que le nombre a effectivement diminué ?
Chaque case cochée élimine du travail inutile à chaque trame.
Vérification rapide
Vous transmettez une List<User> à un composable et remarquez qu'il est recomposé même lorsque les données n'ont pas changé. Quelle est la correction la plus directe ?
Récapitulatif : des recompositions plus limitées et plus rares
Vous avez appris à maîtriser la principale difficulté de performance de Compose : la recomposition :
- Mesurez le nombre de recompositions avec l'inspecteur de mise en page ou un compteur de débogage.
- Lisez l'état aussi bas que possible ; différez les lectures avec des lambdas.
- Rendez les paramètres stables avec des données immuables et
@Immutable/ImmutableList. - Utilisez
rememberetderivedStateOfpour éviter de recalculer à chaque trame. - Attribuez des clés stables aux éléments différés ; gardez les lectures d'état rapides dans des lambdas de modificateur.
Nous passons ensuite du CPU et de la recomposition à la mémoire, afin de trouver et corriger les fuites.
Questions Fréquemment Posées
La leçon « Maîtriser la recomposition » est-elle gratuite ?
Oui — le texte complet de « Maîtriser la recomposition » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Android Academy, passe à CoddyKit PRO. Le cours Android Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Maîtriser la recomposition » ?
Des paramètres stables et moins de recompositions. Tu pratiques Android Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Android Academy ?
Aucune expérience préalable n'est requise. Android Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « Maîtriser la recomposition » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Android Academy ?
Oui. Chaque leçon Android Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Mesurer les performances
- Maîtriser la recomposition
- Fuites mémoire et corrections
- Démarrage et profils de référence