Lorsque vous rencontrez 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
Essayez d'abord d'activer le mode de désactivation renforcée. Le mode de désactivation forte permet d'ignorer 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 la stabilité.
Pour en savoir plus, consultez la section Ignorance fortement forte.
Rendre la classe immuable
Vous pouvez également essayer de rendre une classe instable complètement immuable.
- Immuable: indique un type dans lequel la valeur de n'importe quelle propriété ne peut jamais changer après la construction d'une instance de ce type et où toutes les méthodes sont référentielles transparentes.
- Assurez-vous que toutes les propriétés de la classe sont à la fois de type
val
et nonvar
, et de types immuables. - Les types primitifs comme
String, Int
etFloat
sont toujours immuables. - Si ce n'est pas possible, vous devez utiliser l'état Compose pour toutes les propriétés modifiables.
- Assurez-vous que toutes les propriétés de la classe sont à la fois de type
- Stable : indique un type modifiable. L'environnement d'exécution Compose ne sait pas si et quand l'un des comportements des propriétés publiques ou des méthodes du type produira des résultats différents d'une invocation précédente.
Collections immuables
Les collections sont l'une des raisons courantes pour lesquelles Compose considère une classe comme instable. Comme indiqué sur la page Diagnostiquer les problèmes de stabilité, le compilateur Compose ne peut pas être complètement sûr que des collections telles que List, Map
et Set
sont vraiment 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 immuables, et le compilateur Compose les traite comme telles. Cette bibliothèque est toujours en version alpha. Par conséquent, attendez-vous à des modifications de son API.
Reprenons la 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()
…
}
Ensuite, tous les paramètres de la classe sont immuables, et le compilateur Compose la marque comme stable.
Annotez-la 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 remplace ce que le compilateur inférerait à 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 forçage du comportement du compilateur peut entraîner des bugs imprévus, par exemple si votre composable ne se recompose pas lorsque vous vous y attendez.
Si vous pouvez rendre votre classe stable sans annotation, vous devez vous efforcer de la rendre stable de cette manière.
L'extrait de code suivant fournit un exemple minimal d'une 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 des 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 rencontrent le 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 approches sont décrites dans les sous-sections suivantes.
Fichier de configuration
Si vous souhaitez 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 garantir l'immuabilité au moment de la compilation, vous pouvez utiliser une collection immuable kotlinx au lieu de List
.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Si vous ne pouvez pas utiliser de collection immuable, vous pouvez créer la vôtre. Pour ce faire, encapsulez List
dans une classe stable annotée. Un wrapper générique constitue probablement le meilleur choix, 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 maintenant 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 stabilité
À partir de la version 1.5.5 de Compose, 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 bibliothèque standards telles que LocalDateTime
.
Le fichier de configuration est un fichier au format texte brut contenant 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 du 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 qu'elle référence sont explicitement marqués comme stables ou dans un module qui a également été compilé 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 suivre l'une des approches suivantes :
- Ajouter les classes à votre fichier de configuration de compilateur.
- Activez le compilateur Compose sur vos modules de couche de données ou taguez vos classes avec
@Stable
ou@Immutable
, le cas échéant.- Pour ce faire, vous devez ajouter une dépendance Compose à votre couche de données. Toutefois, il ne s'agit que de la dépendance pour l'environnement d'exécution Compose et non pour
Compose-UI
.
- Pour ce faire, vous devez ajouter une dépendance Compose à votre couche de données. Toutefois, il ne s'agit que de la dépendance pour l'environnement d'exécution Compose et non pour
- Dans votre module d'interface utilisateur, encapsulez vos classes de couche de données dans des classes de 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 les problèmes de stabilité, vous ne devez pas essayer de rendre tous les composables désactivables. Cela peut entraîner une optimisation prématurée qui crée plus de problèmes qu'elle n'en résout.
Dans de nombreuses situations, l'annonce désactivable ne présente aucun avantage réel et peut rendre le code difficile à gérer. Exemple :
- Composable qui n'est pas recomposé souvent, voire pas du tout.
- Un composable qui appelle en soi des composables désactivables.
- Composable avec un grand nombre de paramètres avec des implémentations d'égalité coûteuses. Dans ce cas, le coût de la vérification de la modification d'un paramètre pourrait l'emporter sur le coût d'une recomposition bon marché.
Lorsqu'un composable est désactivable, cela entraîne une légère surcharge qui peut ne pas en valoir la peine. Vous pouvez même annoter votre composable de sorte qu'il soit non redémarrable dans les cas où vous déterminez qu'il est plus coûteux que prévu.