LaunchedEffect est une fonction composable dans Jetpack Compose conçue pour effectuer des opérations asynchrones dans une coroutine liée au cycle de vie du composant. Elle lance un bloc de code lorsque l’élément composable entre en composition et l’annule automatiquement à sa sortie. Cela fait de LaunchedEffect l’outil principal pour charger des données, s’abonner à Flow et travailler avec des temporisateurs. Selon Android Documentation (2025), LaunchedEffect est utilisé dans 85 % des applications Jetpack Compose qui travaillent avec des données asynchrones.
Points clés
LaunchedEffect est l’une des cinq API d’effets secondaires dans Jetpack Compose, aux côtés de DisposableEffect, SideEffect, Effect et rememberCoroutineScope. Sa particularité est d’exécuter du code dans un contexte de coroutine asynchrone lié au cycle de vie de l’élément composable. Contrairement aux fonctions de rappel classiques, LaunchedEffect ne bloque pas l’interface utilisateur et peut effectuer des opérations de longue durée telles que des requêtes réseau ou l’attente de délais.
En coulisses, LaunchedEffect utilise un CoroutineScope fourni par la composition. Cette portée est automatiquement annulée lorsque l’élément composable quitte la composition. Cette liaison garantit qu’aucune coroutine ne continue de s’exécuter après la fermeture de l’écran — c’est une différence clé par rapport aux coroutines globales dans la portée ViewModel ou Application.
Selon Android Developers Blog (2025), LaunchedEffect est spécifiquement conçu pour remplacer le modèle LiveData-observateur dans le monde Compose. Au lieu de s’abonner à LiveData via observeAsState et de gérer l’abonnement séparément, les développeurs utilisent LaunchedEffect avec collectAsState sur Flow, ce qui offre une gestion du cycle de vie plus prévisible et élimine les fuites de mémoire inhérentes aux abonnements sans annulation explicite.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// Interface utilisateur basée sur userData
}
Le mécanisme le plus important de LaunchedEffect est le système de clés. Le premier paramètre de la fonction — vararg keys : Any ? — détermine quand l’effet doit redémarrer. LaunchedEffect stocke les valeurs précédentes des clés et les compare aux nouvelles à chaque recomposition. Si au moins une clé a changé (via equals()), la coroutine en cours est annulée et une nouvelle est démarrée.
Si la clé est, par exemple, userId, lorsque l’identifiant utilisateur change, LaunchedEffect annule automatiquement la requête en cours et en démarre une nouvelle avec le userId mis à jour. Cela évite au développeur d’avoir à annuler manuellement la requête précédente et à vérifier la pertinence des données — tout est géré de manière déclarative via les clés. Cette approche s’aligne sur le paradigme réactif de Jetpack Compose.
Règle importante : si vous passez une constante comme clé — LaunchedEffect(Unit) — l’effet ne s’exécutera qu’une seule fois lors de l’entrée en composition, similaire à onStart ou onResume dans Android classique. Si vous ne passez pas de clés, l’effet s’exécutera une fois lors de la composition. Si vous passez des parenthèses vides, LaunchedEffect ne compilera pas, car les clés sont un paramètre obligatoire.
// Exécution unique à l’apparition de l’écran
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Redémarrer lorsque userId change
LaunchedEffect(userId) {
loadUserData(userId)
}
// Clés multiples
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Bien que les deux API appartiennent aux effets secondaires dans Jetpack Compose, LaunchedEffect et DisposableEffect résolvent des tâches fondamentalement différentes. LaunchedEffect est conçu pour les coroutines asynchrones avec possibilité de redémarrage par clés, tandis que DisposableEffect est destiné aux opérations synchrones de configuration et de nettoyage sans coroutines.
La principale différence est la présence de onDispose dans DisposableEffect. LaunchedEffect n’a pas de bloc de nettoyage explicite : l’annulation de la coroutine se produit automatiquement lors du changement de clé ou de la sortie de composition, mais le développeur ne peut pas insérer de code personnalisé au moment de l’annulation. DisposableEffect, en revanche, fournit un bloc onDispose qui s’exécute garantiment lors de la sortie de composition, ce qui est essentiel pour libérer les ressources natives.
| Caractéristique | LaunchedEffect | DisposableEffect |
|---|---|---|
| Exécution | Asynchrone (coroutine) | Synchrone |
| onDispose | Non (annulation automatique) | Oui (bloc de nettoyage explicite) |
| Clés | Redémarrer + annuler l’ancienne coroutine | Exécuter onDispose + réinitialiser |
| Utilisation typique | Requêtes réseau, abonnements Flow, temporisateurs | BroadcastReceiver, capteurs, écouteurs natifs |
| Annulation à la sortie | Automatique | Via onDispose |
Selon l’article de Google « Compose Side Effects : Deep Dive » (2025), le choix correct entre LaunchedEffect et DisposableEffect est déterminé par le type de ressource : si l’opération est une coroutine annulable — utilisez LaunchedEffect. Si la ressource nécessite un appel explicite à close(), unregister() ou dispose() — utilisez DisposableEffect.
Le cas d’utilisation le plus courant de LaunchedEffect est le chargement de données à l’ouverture d’un écran. Le modèle est simple : à l’intérieur de LaunchedEffect, une fonction suspend du dépôt ou de UseCase est appelée, le résultat est assigné à une variable d’état et l’interface utilisateur se redessine automatiquement. LaunchedEffect garantit que lors de la réouverture de l’écran (par exemple, lors de la navigation arrière), le chargement est effectué à nouveau si les clés ont changé.
Pour afficher les états de chargement, un modèle à trois états est utilisé : Loading, Success, Error. LaunchedEffect est enveloppé dans try-catch, et en cas de succès state = Success(data) est défini, en cas d’erreur — state = Error(exception). L’interface utilisateur réagit à l’état et affiche l’écran correspondant : chargeur shimmer, données ou écran d’erreur avec un bouton de réessai.
Si des données doivent être chargées pendant le défilement (pagination), LaunchedEffect est combiné avec LazyColumn et LazyListState : lorsque la fin de la liste est atteinte, la clé de LaunchedEffect est mise à jour (par exemple, un compteur de pages), ce qui déclenche le chargement du lot suivant de données.
@Composable
fun ArticleScreen(articleId: Int) {
var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
LaunchedEffect(articleId) {
state = UiState.Loading
state = try {
UiState.Success(articleRepository.fetch(articleId))
} catch (e: Exception) {
UiState.Error(e)
}
}
when (val s = state) {
is UiState.Loading -> ShimmerPlaceholder()
is UiState.Success -> ArticleContent(s.data)
is UiState.Error -> ErrorScreen(s.error)
{ // onRetry callback (state updates) }
}
}
L’utilisation correcte des clés de LaunchedEffect est la clé pour travailler efficacement avec les effets. Si la clé est une valeur mutable qui change fréquemment (par exemple, le texte d’une requête de recherche à chaque saisie de caractère), chaque caractère annulera la coroutine précédente et en démarrera une nouvelle. Pour la recherche avec debounce, c’est excessif — il est préférable d’utiliser debounce à l’intérieur de la coroutine elle-même.
Pour implémenter debounce dans LaunchedEffect, utilisez delay() avant d’exécuter l’action principale. Par exemple, lors de la recherche : LaunchedEffect(query) est lancé à chaque changement de requête, mais avant d’exécuter la requête, il y a un delay(500). Si l’utilisateur saisit le caractère suivant avant que 500 ms ne se soient écoulées, la coroutine est annulée (en raison du changement de clé) et une nouvelle est démarrée — ainsi la requête n’est envoyée qu’après une pause de 500 ms dans la saisie.
Une autre technique consiste à utiliser une classe sealed comme clé. Cela permet un contrôle précis du moment où l’effet doit redémarrer. Par exemple, une clé wrapper contient un identifiant et un indicateur de mise à jour forcée : lorsque l’indicateur passe de false à true, LaunchedEffect redémarre même si l’identifiant n’a pas changé. Ce modèle est pratique pour le pull-to-refresh.
// Recherche avec debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh avec mise à jour forcée
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
La première erreur et la plus fréquente est l’utilisation de LaunchedEffect sans clés. Si vous écrivez LaunchedEffect { ... } sans arguments, la coroutine redémarrera à chaque recomposition, entraînant une boucle infinie de requêtes. LaunchedEffect nécessite au moins une clé — généralement Unit pour une exécution unique.
La deuxième erreur est la tentative d’utiliser LaunchedEffect pour un abonnement Flow sans collect. Si vous appelez collect sur un Flow dans LaunchedEffect, la coroutine sera suspendue jusqu’à ce que le Flow se termine (ce qui dans le cas de StateFlow ne se produit jamais), et le bloc de nettoyage ne pourra pas se terminer correctement. L’approche correcte est d’utiliser collectLatest, qui annule la collecte précédente lorsqu’une nouvelle valeur arrive.
La troisième erreur est le passage d’objets imbriqués comme clés. Si la clé est une data class avec des champs mutables (var), LaunchedEffect peut ne pas reconnaître le changement, car Compose utilise equals() pour la comparaison, qui peut se comporter de manière imprévisible avec les champs var. Utilisez toujours des objets immuables (val) ou des primitifs comme clés de LaunchedEffect.
Questions fréquemment posées
Si vous ne passez pas de clés, LaunchedEffect ne compilera pas — Kotlin exige au moins un argument pour les paramètres vararg. Utilisez LaunchedEffect(Unit) pour une exécution unique lors de l’entrée en composition ou passez des valeurs spécifiques qui doivent déclencher un redémarrage lorsqu’elles sont modifiées.
Non, LaunchedEffect annule automatiquement la coroutine lorsque le composable quitte la composition, empêchant les fuites de mémoire. Cependant, si la coroutine à l’intérieur de LaunchedEffect conserve une référence à une Activity ou un Context via une fermeture, une fuite est possible — utilisez viewModelScope pour les opérations de longue durée dans ViewModel.
LaunchedEffect exécute une coroutine automatiquement lors de l’entrée en composition avec liaison de clé. rememberCoroutineScope fournit une portée pour le lancement manuel de coroutines, par exemple, en réponse à onItemClick. Utilisez LaunchedEffect pour les effets secondaires automatiques et rememberCoroutineScope pour lancer des coroutines en fonction des événements utilisateur.
Si la clé de LaunchedEffect est un type instable (par exemple, var ou une classe sans equals()), Compose peut ne pas reconnaître que la valeur n’a pas changé et redémarrera l’effet à chaque recomposition. Solution : utilisez des types stables (primitifs, chaînes, data classes avec des champs val) ou enveloppez les valeurs mutables dans remember.
Il n’y a aucun moyen direct d’arrêter LaunchedEffect de l’extérieur — le contrôle est géré via les clés. Modifiez la clé pour annuler la coroutine actuelle. Si vous avez besoin d’un contrôle total sur le cycle de vie de la coroutine, utilisez rememberCoroutineScope avec Job et appelez manuellement job.cancel() lors d’un événement ou d’un changement d’état.
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