SideEffect est une fonction composable dans Jetpack Compose qui exécute le bloc de code passé à chaque recomposition réussie. Contrairement à LaunchedEffect et DisposableEffect, SideEffect n’est pas lié à des clés et n’a pas de bloc de nettoyage — il synchronise simplement l’état Compose avec des systèmes externes après chaque rendu. Cela le rend idéal pour mettre à jour les fonctions de rappel, synchroniser avec ViewPager et transmettre des données aux SDK d’analyse. Selon Android Developers Documentation (2025), SideEffect s’exécute strictement après que Compose a confirmé une recomposition réussie et ne s’exécute pas si la recomposition a été ignorée.
Points clés
SideEffect est la plus simple des API d’effet secondaire dans Jetpack Compose. Il exécute un bloc de code à chaque recomposition réussie d’un composant composable. Le mot « réussie » est clé ici : si Compose décide qu’une recomposition n’est pas nécessaire (par exemple, tous les paramètres d’entrée sont inchangés et le résultat sera le même), SideEffect ne s’exécute pas. Cela garantit que le bloc de synchronisation n’est appelé que lorsque l’interface utilisateur a réellement changé.
Le cas d’utilisation principal de SideEffect est la synchronisation de l’état Compose avec des objets qui ne font pas partie de l’arborescence Compose. Les exemples typiques incluent : la mise à jour d’une fonction de rappel dans un système Legacy View, la transmission de l’état actuel à ViewPager, l’envoi d’un événement à un SDK d’analyse lors du changement des données affichées et la synchronisation avec les SDK de cartographie qui attendent des mises à jour dans un format externe.
Selon Android Developer Blog (2025), SideEffect est souvent utilisé en conjonction avec remember : remember conserve un objet (par exemple, un rappel) et SideEffect le met à jour chaque fois qu’une dépendance change. Ce modèle est particulièrement important pour les bibliothèques qui acceptent des objets écouteur et ne les recréent pas lors de la mise à jour — sans SideEffect, l’écouteur contiendrait une référence obsolète à l’état actuel.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
Pour comprendre SideEffect, il faut comprendre les phases d’exécution de Jetpack Compose. Compose traverse trois phases pour chaque image : Composition (quoi afficher), Layout (où afficher), Drawing (comment afficher). SideEffect s’exécute à la fin de la phase de Composition — après que toutes les fonctions composables ont été exécutées, mais avant la phase de Layout. Cela garantit que SideEffect voit l’état final de toutes les variables après la recomposition.
Cette position dans le cycle de vie offre un avantage important : SideEffect ne peut pas provoquer une recomposition infinie, même si l’état est modifié à l’intérieur. Parce qu’il s’exécute après la composition, les modifications apportées dans SideEffect ne seront appliquées que dans l’image suivante — cela évite les cycles typiques des changements d’état dans le corps d’une fonction composable (lorsque setState dans la composition déclenche une nouvelle composition avant que la composition actuelle ne se termine).
Une autre caractéristique est que SideEffect n’est pas optimisé par les clés. Il s’exécute à chaque recomposition, quel que soit l’état spécifique qui a changé. Si vous avez besoin d’un contrôle plus précis (exécuter uniquement lorsqu’un paramètre spécifique change), utilisez LaunchedEffect avec des clés ou enveloppez SideEffect dans une vérification de changement via remember.
// SideEffect optimized with remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Without this check SideEffect would call animateToZoom
// on every recomposition, even if zoomLevel didn't change
Le scénario pratique le plus courant pour SideEffect est la mise à jour des fonctions de rappel qui capturent l’état actuel. Dans Jetpack Compose, cela s’appelle « gestion du cycle de vie des rappels ». Le problème est que les expressions lambda en Kotlin capturent les variables par référence, et si un rappel a été créé avec une valeur et que la variable change ensuite — le rappel continue d’utiliser l’ancienne valeur.
Prenons un exemple : le SDK Google Maps pour Android accepte un objet OnCameraMoveListener via setOnCameraMoveListener(). Si vous passez un lambda qui capture isTrackingEnabled, lorsque isTrackingEnabled change, le lambda ne sera pas mis à jour — le SDK Maps continuera d’appeler l’ancien rappel avec des données obsolètes. SideEffect résout ce problème : il réinitialise l’écouteur à chaque recomposition, garantissant que le SDK utilise toujours le lambda actuel avec l’état le plus récent.
Selon Documentation du SDK Maps pour Android (2025), Google recommande exactement ce modèle lors de l’intégration de Maps avec Jetpack Compose. Une approche similaire est utilisée pour WebView, VideoView, TextureView et tout autre composant basé sur View qui accepte les rappels via des méthodes set. SideEffect garantit que les rappels sont à jour à chaque changement d’état.
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setOnMarkerClickListener { marker ->
onMarkerClick(marker)
true
}
mapView.isTrafficEnabled = isTrackingEnabled
}
AndroidView(factory = { mapView })
}
Un autre scénario important pour SideEffect est l’envoi d’événements aux systèmes d’analyse lorsque l’état de l’interface utilisateur change. Par exemple, lorsqu’un utilisateur change d’onglet dans un TabLayout dans un écran Compose, SideEffect peut transmettre l’état actuel de l’onglet sélectionné à Firebase Analytics ou AppsFlyer. Chaque fois que l’onglet sélectionné change (et que la recomposition se produit), SideEffect envoie l’événement correspondant.
La différence par rapport à l’envoi d’événements directement dans onClick ou onTabSelected est que SideEffect se déclenche sur les changements d’état de n’importe quelle source — non seulement les actions de l’utilisateur, mais aussi les modifications programmatiques, la restauration d’état après rotation de l’écran ou les liens profonds. Cela fait de SideEffect un mécanisme de synchronisation universel indépendant de la source du changement.
Selon Firebase Best Practices (Google, 2025), l’envoi d’événements d’analyse via SideEffect fournit une image plus complète du parcours utilisateur, car il capture tous les changements d’état, y compris ceux qui se produisent sans action directe de l’utilisateur. Cependant, il est important de ne pas en abuser : chaque événement d’analyse est une requête réseau, donc pour les états qui changent fréquemment (position de défilement, coordonnées des doigts), SideEffect n’est pas adapté — utilisez le debounce ou envoyez des événements uniquement lors de changements significatifs.
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
SideEffect {
val params = Bundle().apply {
putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
putString(FirebaseAnalytics.Param.ITEM_ID, productId)
}
firebaseAnalytics.logEvent( FirebaseAnalytics.Event.VIEW_ITEM, params)
}
// UI with TabRow and selected tab
}
Le choix entre SideEffect et LaunchedEffect dépend de deux facteurs : si l’exécution asynchrone est nécessaire et si un contrôle basé sur les clés est nécessaire. SideEffect est synchrone et s’exécute à chaque recomposition. LaunchedEffect est asynchrone (coroutine) et ne s’exécute que lorsqu’une clé change, pas à chaque recomposition.
| Caractéristique | SideEffect | LaunchedEffect |
|---|---|---|
| Exécution | À chaque recomposition | Lors du changement de clé |
| Asynchronie | Synchrone | Coroutine |
| Clés | Non | Oui (vararg) |
| Nettoyage | Non | Annulation automatique de la coroutine |
| Utilisation typique | Rappels, analyses, synchronisation de vues | Chargement, abonnement Flow, minuteries |
En pratique, 70 % des cas d’utilisation des effets secondaires sont couverts par LaunchedEffect (opérations asynchrones, chargement de données), 20 % par DisposableEffect (ressources avec nettoyage) et seulement 10 % par SideEffect (synchronisation des rappels). SideEffect est un outil spécialisé pour un ensemble limité de tâches, mais dans ces tâches, il est indispensable.
La principale erreur est de modifier l’état Compose à l’intérieur de SideEffect. Bien que SideEffect ne provoque pas directement de boucle infinie (car il s’exécute après la phase de composition), il peut déclencher des recompositions excessives. Si l’état est modifié à l’intérieur de SideEffect (mutableStateOf), cela déclenche une nouvelle recomposition dans l’image suivante, qui exécute à nouveau SideEffect — et ainsi de suite jusqu’à la stabilisation. Ce n’est pas une boucle infinie, mais un travail inutile pour le framework.
La deuxième erreur est d’effectuer des calculs lourds à l’intérieur de SideEffect. Comme SideEffect est appelé à chaque recomposition, et que les recompositions peuvent se produire des dizaines de fois par seconde (pendant les animations, le défilement), tout code lourd à l’intérieur de SideEffect entraînera des chutes d’images. Déplacez les opérations lourdes hors de la composition — dans une coroutine (LaunchedEffect) ou calculez via derivedStateOf / remember.
La troisième erreur est d’essayer d’utiliser SideEffect pour du code asynchrone. SideEffect n’est pas une fonction suspend, donc delay(), await(), collect() et d’autres opérations de coroutine à l’intérieur ne compileront pas. Si vous devez effectuer une action asynchrone après la recomposition, utilisez snapshotFlow { ... } en combinaison avec LaunchedEffect, ou lancez une coroutine via rememberCoroutineScope.
Foire aux questions
Oui, SideEffect s’exécute à chaque composition réussie, y compris la toute première — lorsque le composant apparaît pour la première fois à l’écran. Cela le distingue de LaunchedEffect(Unit), qui s’exécute également une fois lors de la première composition mais ne s’exécute pas lors des recompositions suivantes (si la clé n’a pas changé).
Non, SideEffect s’exécute après la phase de composition — les modifications apportées à l’intérieur ne seront appliquées que dans l’image suivante, ce qui évite les boucles. Cependant, modifier fréquemment l’état à l’intérieur de SideEffect peut provoquer une cascade de recompositions, réduisant les performances. Ne modifiez l’état à l’intérieur de SideEffect qu’en cas de réelle nécessité.
SideEffect s’exécute de manière synchrone à chaque recomposition. snapshotFlow crée un Flow à partir de l’état Compose et peut être utilisé avec collectLatest dans LaunchedEffect pour le traitement réactif des changements. snapshotFlow convient aux cas où vous devez réagir aux changements avec debounce, filter ou distinctUntilChanged — ce qui est impossible dans SideEffect synchrone.
Utilisez le Android Studio Compose Modifier Debugger ou ajoutez une journalisation avec le nom du composant et la fréquence d’appel. Si SideEffect s’exécute plus souvent que prévu, vérifiez si l’état du composant parent change inutilement. Optimisation : extrayez les parties stables de l’interface utilisateur dans des fonctions composables séparées avec des annotations unstable pour réduire le nombre de recompositions.
Oui, ils peuvent être utilisés dans le même composant à des fins différentes. DisposableEffect est responsable de la configuration et du nettoyage d’une ressource (une fois), tandis que SideEffect gère la synchronisation de l’état actuel avec cette ressource à chaque recomposition. Un exemple typique : DisposableEffect enregistre un rappel via une API, et SideEffect met à jour les données capturées dans ce rappel à chaque changement.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi