Face à une classe instable qui entraîne des problèmes de performances, vous devez la rendre stable. Ce document décrit plusieurs techniques que vous pouvez utiliser pour ce faire.
Activer la désactivation renforcée
Vous devez d'abord essayer d'activer le mode de désactivation renforcée. Le mode de désactivation renforcée permet de désactiver les composables avec des paramètres instables. Il s'agit de la méthode la plus simple pour résoudre les problèmes de performances causés par l'instabilité.
Pour en savoir plus, consultez Ignorer fortement.
Rendre la classe immuable
Vous pouvez également essayer de rendre une classe instable complètement immuable.
- Immuable : indique un type dont la valeur des propriétés ne peut jamais changer une fois qu'une instance de ce type est construite et que toutes les méthodes sont référentiellement transparentes.
- Assurez-vous que toutes les propriétés de la classe sont à la fois
valplutôt quevaret de types immuables. - Les types primitifs tels que
String, IntetFloatsont toujours immuables. - Si cela n'est pas possible, vous devez utiliser l'état Compose pour toutes les propriétés mutables.
- Assurez-vous que toutes les propriétés de la classe sont à la fois
- Stable : indique un type mutable. L'exécution Compose n'est pas informée si et quand l'une des propriétés publiques ou le comportement de la méthode du type produiraient des résultats différents d'une invocation précédente.
Collections immuables
Les collections sont une cause fréquente d'instabilité d'une classe pour Compose. Comme indiqué sur la page Diagnostiquer les problèmes de stabilité, le compilateur Compose ne peut pas être totalement sûr que les collections telles que List, Map et Set sont réellement immuables. Il les marque donc comme instables.
Pour résoudre ce problème, vous pouvez utiliser des collections immuables. Le compilateur Compose est compatible avec les collections immuables Kotlinx. Ces collections sont garanties d'être immuables, et le compilateur Compose les traite comme telles. Cette bibliothèque est encore en version alpha. Des modifications de son API sont donc possibles.
Reprenons cette classe instable du guide Diagnostiquer les problèmes de stabilité :
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Vous pouvez rendre tags stable à l'aide d'une collection immuable. Dans la classe, remplacez le type de tags par ImmutableSet<String> :
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Une fois cette opération effectuée, tous les paramètres de la classe sont immuables et le compilateur Compose marque la classe comme stable.
Annoter avec Stable ou Immutable
Pour résoudre les problèmes de stabilité, vous pouvez annoter les classes instables avec @Stable ou @Immutable.
Annoter une classe revient à remplacer ce que le compilateur déduirait autrement à propos de votre classe. Il est semblable à l'opérateur !! en Kotlin. Vous devez faire très attention à la façon dont vous utilisez ces annotations. Le remplacement du comportement du compilateur peut entraîner des bugs imprévus, par exemple si votre composable ne se recompose pas alors que vous vous y attendiez.
Si vous pouvez rendre votre classe stable sans annotation, vous devez vous efforcer d'atteindre la stabilité de cette manière.
L'extrait de code suivant fournit un exemple minimal de classe de données annotée comme immuable :
@Immutable
data class Snack(
…
)
Que vous utilisiez l'annotation @Immutable ou @Stable, le compilateur Compose marque la classe Snack comme stable.
Classes annotées dans les collections
Prenons l'exemple d'un composable qui inclut un paramètre de type List<Snack> :
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Même si vous annotez Snack avec @Immutable, le compilateur Compose marque toujours le paramètre snacks dans HighlightedSnacks comme instable.
Les paramètres sont confrontés au même problème que les classes en ce qui concerne les types de collections : le compilateur Compose marque toujours un paramètre de type List comme instable, même s'il s'agit d'une collection de types stables.
Vous ne pouvez pas marquer un paramètre individuel comme stable, ni annoter un composable pour qu'il soit toujours ignorable. Plusieurs options s'offrent à vous.
Il existe plusieurs façons de contourner le problème des collections instables. Ces différentes approches sont décrites dans les sous-sections suivantes.
Fichier de configuration
Si vous acceptez de respecter le contrat de stabilité dans votre codebase, vous pouvez choisir de considérer les collections Kotlin comme stables en ajoutant kotlin.collections.* à votre fichier de configuration de stabilité.
Collection immuable
Pour la sécurité de l'immuabilité au moment de la compilation, vous pouvez utiliser une collection kotlinx immutable au lieu de List.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Si vous ne pouvez pas utiliser de collection immuable, vous pouvez en créer une. Pour ce faire, enveloppez List dans une classe stable annotée. Un wrapper générique est probablement le meilleur choix pour cela, en fonction de vos besoins.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Vous pouvez ensuite l'utiliser comme type de paramètre dans votre composable.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Solution
Après avoir adopté l'une de ces approches, le compilateur Compose marque désormais le composable HighlightedSnacks comme skippable et restartable.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
Lors de la recomposition, Compose peut désormais ignorer HighlightedSnacks si aucune de ses entrées n'a changé.
Fichier de configuration de la stabilité
À partir de Compose Compiler 1.5.5, un fichier de configuration des classes à considérer comme stables peut être fourni au moment de la compilation. Cela permet de considérer comme stables les classes que vous ne contrôlez pas, telles que les classes de la bibliothèque standard comme LocalDateTime.
Le fichier de configuration est un fichier en texte brut avec une classe par ligne. Les commentaires, les caractères génériques simples et doubles sont acceptés.
Exemple de configuration :
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
Pour activer cette fonctionnalité, transmettez le chemin d'accès au fichier de configuration au bloc d'options composeCompiler de la configuration du plug-in Gradle du compilateur Compose.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Étant donné que le compilateur Compose s'exécute séparément sur chaque module de votre projet, vous pouvez fournir différentes configurations à différents modules si nécessaire. Vous pouvez également avoir une configuration au niveau racine de votre projet et transmettre ce chemin d'accès à chaque module.
Plusieurs modules
Un autre problème courant concerne l'architecture multimodule. Le compilateur Compose ne peut déduire si une classe est stable que si tous les types non primitifs auxquels elle fait référence sont explicitement marqués comme stables ou se trouvent dans un module qui a également été créé avec le compilateur Compose.
Si votre couche de données se trouve dans un module distinct de votre couche d'UI, ce qui est l'approche recommandée, vous pouvez rencontrer ce problème.
Solution
Pour résoudre ce problème, vous pouvez procéder de l'une des façons suivantes :
- Ajoutez les classes à votre fichier de configuration du compilateur.
- Activez le compilateur Compose sur vos modules de couche de données ou taguez vos classes avec
@Stableou@Immutable, le cas échéant.- Pour ce faire, vous devez ajouter une dépendance Compose à votre couche de données. Toutefois, il s'agit uniquement de la dépendance pour le runtime Compose et non pour
Compose-UI.
- Pour ce faire, vous devez ajouter une dépendance Compose à votre couche de données. Toutefois, il s'agit uniquement de la dépendance pour le runtime Compose et non pour
- Dans votre module d'UI, encapsulez vos classes de couche de données dans des classes wrapper spécifiques à l'UI.
Le même problème se produit également lorsque vous utilisez des bibliothèques externes si elles n'utilisent pas le compilateur Compose.
Tous les composables ne doivent pas être ignorables
Lorsque vous essayez de résoudre des problèmes de stabilité, n'essayez pas de rendre chaque composable ignorable. Si vous essayez de le faire, vous risquez d'introduire une optimisation prématurée qui entraînera plus de problèmes qu'elle n'en résoudra.
Dans de nombreuses situations, le fait d'être ignorable n'a aucun avantage réel et peut entraîner un code difficile à entretenir. Exemple :
- Composable qui n'est pas recomposé souvent, voire pas du tout.
- Composable qui appelle en lui-même des composables pouvant être ignorés.
- Composable avec un grand nombre de paramètres avec des implémentations equals coûteuses. Dans ce cas, le coût de la vérification de la modification d'un paramètre peut être supérieur à celui d'une recomposition bon marché.
Lorsqu'un composable est ignorable, il ajoute une petite surcharge qui peut ne pas en valoir la peine. Vous pouvez même annoter votre composable pour qu'il soit non redémarrable dans les cas où vous déterminez que le fait d'être redémarrable représente plus de surcharge que d'avantages.