DisposableEffect est une fonction composable dans Jetpack Compose conçue pour les opérations nécessitant une initialisation explicite et un nettoyage ultérieur des ressources. Contrairement aux autres API d’effets secondaires, DisposableEffect fournit un bloc onDispose qui est garanti de s’exécuter lorsque le composant quitte la composition ou lorsque la clé change. Cela le rend indispensable pour travailler avec des abonnements natifs, des écouteurs de capteurs et des ressources matérielles. Selon Android Developers Documentation (2025), DisposableEffect est recommandé dans tous les scénarios nécessitant une paire setup/teardown, analogue à onStart/onStop dans le cycle de vie d’Activity.
Points clés
DisposableEffect est un outil clé pour la gestion des ressources dans Jetpack Compose. Sa principale caractéristique est l’invocation garantie du bloc onDispose lorsque le cycle de vie du composant composable se termine. Ce comportement est crucial pour le développement Android, où les abonnements non fermés aux services système peuvent entraîner des fuites mémoire et des crashs d’application.
Contrairement à LaunchedEffect, qui s’exécute dans un contexte asynchrone de coroutine, DisposableEffect s’exécute de manière synchrone. Cela signifie que vous ne pouvez pas appeler de fonctions suspend à l’intérieur. L’exécution synchrone garantit la prévisibilité : vous pouvez être sûr que le code d’initialisation s’exécute avant le premier rendu et que le code de nettoyage s’exécute avant que le composant ne soit retiré de la mémoire.
Selon la Documentation Jetpack Compose (2025), DisposableEffect doit être utilisé dans quatre scénarios principaux : (1) abonnement aux services système (capteurs, LocationManager), (2) enregistrement de BroadcastReceiver, (3) travail avec des bibliothèques basées sur des callbacks ne supportant pas les coroutines, (4) liaison de composants Compose aux systèmes View legacy via AndroidView.
class SensorManager(private val context: Context) {
fun startListening(callback: (Float) -> Unit) { /* register */ }
fun stopListening() { /* cancel */ }
}
@Composable
fun SensorDisplay() {
val sensorManager = remember { SensorManager(context) }
var value by remember { mutableStateOf(0f) }
DisposableEffect(Unit) {
sensorManager.startListening { value = it }
onDispose { sensorManager.stopListening() }
}
Text("Capteur : $value")
}
Le mécanisme interne de DisposableEffect est basé sur les phases du cycle de vie de la composition. Lorsqu’un composant composable entre dans la composition, DisposableEffect exécute le bloc de code passé. Ce bloc retourne un objet DisposableEffectResult contenant la lambda onDispose. La composition enregistre ce résultat et appelle onDispose lorsque le composant quitte la composition — quelle que soit la raison (navigation, changement d’état parent, suppression de LazyColumn).
Le mécanisme de clés dans DisposableEffect fonctionne de manière similaire à LaunchedEffect : lorsqu’une clé change, onDispose est d’abord exécuté pour l’ancien état, puis le bloc d’initialisation est relancé avec les nouvelles clés. Cela permet de reconfigurer une ressource lorsque ses paramètres changent. Par exemple, si la clé est une URL de socket, lorsqu’elle change, l’ancien socket est fermé et un nouveau est ouvert.
Important : le bloc onDispose est un élément obligatoire de DisposableEffect. Si vous n’appelez pas onDispose à l’intérieur du bloc, le code ne compilera pas. Cette exigence du compilateur garantit que le développeur n’oublie pas de prévoir le nettoyage des ressources, ce qui est une cause fréquente d’erreurs dans la gestion manuelle des abonnements.
// Utilisation correcte avec une clé
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Plusieurs ressources dans un seul DisposableEffect
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Les fuites mémoire dans les applications Android surviennent souvent à cause d’écouteurs et d’abonnements non désenregistrés qui continuent de conserver une référence à une Activity ou un Context après la fermeture de l’écran. DisposableEffect résout ce problème au niveau du framework : si le développeur utilise DisposableEffect pour enregistrer un écouteur, onDispose garantit d’annuler l’abonnement dans tout scénario de fin de composant.
C’est particulièrement crucial pour LazyColumn et LazyGrid, où les éléments sont constamment créés et détruits au fur et à mesure que l’utilisateur fait défiler. Sans DisposableEffect, chaque élément qui disparaît de la zone visible laisserait un abonnement actif. Avec DisposableEffect, onDispose est appelé pour chaque élément déchargé, garantissant que les ressources sont libérées immédiatement après que l’élément quitte l’écran.
Selon Android Performance Patterns (Google, 2025), l’utilisation de DisposableEffect pour tous les abonnements natifs réduit le nombre de fuites mémoire dans les applications Compose de 60–70% par rapport à la gestion manuelle via des callbacks de cycle de vie. Le système lui-même suit le moment où le composant quitte la composition et garantit l’exécution de onDispose même lors de la fermeture d’urgence de l’écran.
| Ressource | Ce que fait DisposableEffect | Sans DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Le récepteur reste actif |
| SensorManager | registerListener + onDispose → unregisterListener | Le capteur continue d’envoyer des données |
| Observable (pas Flow) | subscribe + onDispose → unsubscribe | Le callback conserve une référence |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Fuite de callback |
| Socket / Channel | open + onDispose → close | La connexion reste ouverte |
L’un des exemples les plus illustratifs de l’utilisation de DisposableEffect est le travail avec les capteurs de l’appareil (accéléromètre, gyroscope, magnétomètre). Les capteurs nécessitent une annulation d’enregistrement obligatoire à la fin, sinon ils continuent de consommer de l’énergie de la batterie et d’envoyer des données même après la fermeture de l’écran.
Exemple pratique : une application de mesure d’angle d’inclinaison. DisposableEffect(Unit) enregistre un écouteur d’accéléromètre lorsque le composant apparaît et annule l’enregistrement dans onDispose. Les données du capteur sont transmises à l’état via mutableStateOf, ce qui met à jour automatiquement l’interface. Si l’écran défile dans LazyColumn et que l’élément disparaît, onDispose se déclenche immédiatement — le capteur cesse d’envoyer des données pour cet élément.
Lors du changement de type de capteur (par exemple, d’accéléromètre à gyroscope), la clé sensorType change, onDispose annule l’ancien abonnement et le nouveau bloc DisposableEffect enregistre le nouveau capteur. Sans clés, vous devriez vérifier manuellement quel capteur était enregistré précédemment et appeler unregisterListener avec le bon écouteur — ce qui est sujet aux erreurs.
@Composable
fun SensorReadingScreen(sensorType: Int) {
val context = LocalContext.current
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
var sensorValue by remember { mutableStateOf(0f) }
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
val listener = SensorEventListener { event, _ ->
sensorValue = event.values[0]
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
Text("Valeur : $sensorValue")
}
BroadcastReceiver est un exemple classique d’API nécessitant une paire obligatoire register / unregister. Dans une application Compose, DisposableEffect est idéal pour enregistrer un récepteur pendant la durée de vie d’un écran spécifique. À l’entrée de l’écran, un BroadcastReceiver avec le IntentFilter nécessaire est enregistré ; à la sortie, il est automatiquement annulé dans onDispose.
Un scénario typique est la surveillance de l’état du réseau. DisposableEffect enregistre un récepteur pour ConnectivityManager qui notifie des changements de connectivité réseau. Lorsque le statut change (WiFi / données mobiles / pas de réseau), l’état du composable est mis à jour et l’interface affiche l’indicateur correspondant. Lorsque l’écran se ferme, onDispose garantit l’annulation de l’enregistrement — même si l’application passe en arrière-plan.
Pour les récepteurs utilisant ContextCompat.registerReceiver avec le flag RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+), l’utilisation de DisposableEffect devient obligatoire car le système exige une spécification explicite de la portée du récepteur. DisposableEffect garantit que la portée est limitée à la durée de vie de l’écran, ce qui correspond aux exigences de sécurité des versions récentes d’Android.
@Composable
fun NetworkStatusBanner() {
val context = LocalContext.current
var isConnected by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
val receiver = BroadcastReceiver { _, _ ->
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
isConnected = cm.getActiveNetwork() != null
}
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
context.registerReceiver(receiver, filter)
}
onDispose {
context.unregisterReceiver(receiver)
}
}
if (!isConnected) { ... }
}
La première erreur critique est l’absence d’appel à onDispose. Le code à l’intérieur du bloc DisposableEffect doit appeler onDispose, sinon une erreur de compilation se produit. Cependant, les développeurs essaient parfois de contourner cela en plaçant onDispose dans une condition : if (condition) { onDispose { ... } }. Ce code compilera, mais onDispose ne sera pas enregistré si la condition n’est pas remplie — la ressource ne sera jamais libérée.
La deuxième erreur est l’utilisation de DisposableEffect pour des opérations asynchrones. DisposableEffect étant synchrone, vous ne pouvez pas écrire d’appels delay() ou await() à l’intérieur. Si vous avez besoin d’une initialisation asynchrone avec nettoyage ultérieur, utilisez une combinaison de LaunchedEffect (pour le chargement des données) et DisposableEffect (pour la configuration/le nettoyage des ressources natives), ou utilisez un mécanisme séparé avec rememberCoroutineScope.
La troisième erreur est la création de nouveaux objets à l’intérieur de DisposableEffect sans remember. Si des objets (capteur, écouteur, récepteur) sont créés à l’intérieur de l’effet à chaque appel et que les clés changent fréquemment, cela entraîne une création excessive d’objets et un ramassage de déchets. Il est préférable de déplacer la création d’objets dans remember ou remember { ... } en dehors de DisposableEffect, et de seulement les enregistrer et les annuler à l’intérieur de l’effet.
Questions fréquemment posées
DisposableEffect fonctionne de manière synchrone et fournit onDispose pour un nettoyage explicite des ressources. LaunchedEffect fonctionne de manière asynchrone dans une coroutine et l’annule automatiquement lors du changement de clé ou de la sortie de la composition. Si une ressource nécessite l’appel d’une méthode de nettoyage (close, unregister, dispose) — utilisez DisposableEffect. Si l’opération est une fonction suspend — utilisez LaunchedEffect.
Oui, onDispose est obligatoire — le compilateur Kotlin exige qu’il soit appelé à l’intérieur du bloc DisposableEffect. Si vous n’appelez pas onDispose, le code ne compilera pas. Cela a été fait intentionnellement pour empêcher les développeurs d’oublier et garantir que chaque ressource ouverte soit correctement fermée lors de la sortie de la composition.
Utilisez try-catch à l’intérieur du bloc DisposableEffect. Si l’enregistrement de la ressource peut lever une exception (par exemple, capteur introuvable), enveloppez-le dans try et traitez l’erreur dans l’interface via un état séparé. onDispose doit être appelé indépendamment du succès de l’initialisation — placez-le dans un bloc finally ou à la fin de la section try.
Non recommandé. Pour Flow, il est préférable d’utiliser LaunchedEffect avec collectLatest ou la méthode .collectAsState() avec Lifecycle.repeatOnLifecycle. DisposableEffect ne supporte pas les fonctions suspend, donc s’abonner à un Flow à l’intérieur nécessiterait de lancer une coroutine séparée via CoroutineScope, ce qui complique le code et augmente le risque de fuites.
Il n’y a pas de limites, mais il est recommandé de regrouper les ressources connexes dans un seul DisposableEffect avec plusieurs opérations à l’intérieur et un seul onDispose. Si les ressources sont indépendantes (par exemple, capteur et BroadcastReceiver), il est préférable de les diviser en DisposableEffects séparés avec des clés différentes — cela simplifie le débogage et évite la recréation non souhaitée de toutes les ressources lors du changement d’une clé.
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