LaunchedEffect: wat is het, coroutines en beheer in Jetpack Compose

Auteur: IT Sectr Gepubliceerd: 2026-06-30 Leestijd: 9 min

LaunchedEffect — is een composable-functie in Jetpack Compose, ontworpen voor het uitvoeren van asynchrone bewerkingen binnen een coroutine die is gekoppeld aan de levenscyclus van de component. Het start een codeblok bij binnenkomst van het composable-element in de compositie en annuleert het automatisch bij vertrek. Dit maakt LaunchedEffect het belangrijkste hulpmiddel voor het laden van gegevens, abonneren op Flow en werken met timers. Volgens Android Documentation (2025) wordt LaunchedEffect gebruikt in 85% van de Jetpack Compose-applicaties die met asynchrone gegevens werken.

Belangrijkste punten

  • LaunchedEffect — side-effect API voor het starten van coroutines in de context van compositie.
  • Sleutels — bij wijziging van sleutels wordt de coroutine geannuleerd en herstart met nieuwe waarden.
  • Auto-annulering — de coroutine wordt automatisch geannuleerd bij het verlaten van component uit de compositie.
  • Asynchroniteit — het blok wordt uitgevoerd in CoroutineScope met dispatcher Dispatchers.Main.
  • Gegevens laden — typisch scenario: laden vanuit het netwerk bij eerste weergave van het scherm.

Wat is LaunchedEffect in Jetpack Compose

LaunchedEffect — is een van de vijf side-effect API's in Jetpack Compose, naast DisposableEffect, SideEffect, SnapshotEffect en rememberCoroutineScope. Het bijzondere ervan is het uitvoeren van code in de asynchrone context van een coroutine die is gekoppeld aan de levenscyclus van het composable-element. In tegenstelling tot gewone callback-functies blokkeert LaunchedEffect de UI niet en kan het langdurige bewerkingen uitvoeren, zoals netwerkverzoeken of wachten op een vertraging.

Onder de motorkap gebruikt LaunchedEffect CoroutineScope, die wordt geleverd door de compositie. Deze scope wordt automatisch geannuleerd wanneer het composable-element de compositie verlaat. Een dergelijke koppeling garandeert dat geen enkele coroutine blijft draaien nadat het scherm is gesloten — dit is het belangrijkste verschil met globale coroutines in ViewModel of Application scope.

Volgens Android Developers Blog (2025) is LaunchedEffect speciaal ontworpen om het LiveData-observer patroon in de Compose-wereld te vervangen. In plaats van te abonneren op LiveData via observeAsState en het abonnement apart te beheren, gebruiken ontwikkelaars LaunchedEffect met collectAsState op Flow, wat een beter voorspelbaar levenscyclusbeheer biedt en geheugenlekken elimineert die kenmerkend zijn voor abonnementen zonder expliciete annulering.

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // UI gebaseerd op userData
}

Hoe werkt LaunchedEffect met sleutels

Het belangrijkste mechanisme van LaunchedEffect — is het sleutelsysteem. De eerste parameter van de functie — vararg keys: Any? — bepaalt wanneer het effect opnieuw moet worden gestart. LaunchedEffect slaat de vorige sleutelwaarden op en vergelijkt ze bij elke recompositie met de nieuwe. Als ten minste één sleutel is veranderd (via equals()), wordt de huidige coroutine geannuleerd en een nieuwe gestart.

Als de sleutel bijvoorbeeld userId is, zal LaunchedEffect bij wijziging van de gebruikersidentificatie het huidige verzoek automatisch annuleren en een nieuw verzoek starten met de bijgewerkte userId. Dit bespaart de ontwikkelaar het handmatig annuleren van het vorige verzoek en het controleren van de actualiteit van gegevens — alles wordt declaratief beheerd via sleutels. Een dergelijke benadering komt overeen met het reactieve paradigma van Jetpack Compose.

Belangrijke regel: als een constante als sleutel wordt doorgegeven — LaunchedEffect(Unit) — wordt het effect slechts eenmaal uitgevoerd bij binnenkomst in de compositie, als analogon van onStart of onResume in klassiek Android. Als er geen sleutels worden doorgegeven — LaunchedEffect(Unit) — werkt het effect eenmalig. Als er lege haakjes worden doorgegeven — LaunchedEffect wordt niet gecompileerd, omdat sleutels een verplichte parameter zijn.

kotlin
// Eenmalige uitvoering wanneer het scherm verschijnt
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// Herstart wanneer userId verandert
LaunchedEffect(userId) {
    loadUserData(userId)
}

// Meerdere sleutels
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

Verschil tussen LaunchedEffect en DisposableEffect

Hoewel beide API's tot side effects in Jetpack Compose behoren, lossen LaunchedEffect en DisposableEffect fundamenteel verschillende taken op. LaunchedEffect is bedoeld voor asynchrone coroutines met de mogelijkheid om opnieuw te starten op basis van sleutels, terwijl DisposableEffect voor synchrone configuratie- en opschoningsbewerkingen zonder coroutines is.

Het belangrijkste verschil — de aanwezigheid van onDispose in DisposableEffect. LaunchedEffect heeft geen expliciet opschoningsblok: annulering van de coroutine gebeurt automatisch bij sleutelwijziging of verlaten van de compositie, maar de ontwikkelaar kan geen eigen code invoegen op het moment van deze annulering. DisposableEffect biedt daarentegen het onDispose-blok, dat gegarandeerd wordt uitgevoerd bij het verlaten van de compositie, wat cruciaal is voor het vrijgeven van native bronnen.

KenmerkLaunchedEffectDisposableEffect
UitvoeringAsynchroon (coroutine)Synchroon
onDisposeNee (auto-annulering coroutine)Ja (expliciet opschoningsblok)
SleutelsHerstart + annulering oude coroutineUitvoering onDispose + herinitialisatie
Typische toepassingNetwerkverzoeken, Flow-abonnementen, timersBroadcastReceiver, sensoren, native listeners
Annulering bij vertrekAutomatischVia onDispose

Volgens Google-artikel „Compose Side Effects: Deep Dive” (2025) wordt de juiste keuze tussen LaunchedEffect en DisposableEffect bepaald door het type bron: als de bewerking een coroutine is met annuleringsmogelijkheid — gebruik LaunchedEffect. Als de bron een expliciete aanroep van close(), unregister() of dispose() vereist — gebruik DisposableEffect.

Gegevens laden via LaunchedEffect

Het meest voorkomende gebruiksscenario van LaunchedEffect — het laden van gegevens bij het openen van het scherm. Het patroon is eenvoudig: binnen LaunchedEffect wordt de suspend-functie van de repository of UseCase aangeroepen, het resultaat wordt toegewezen aan een state-variabele en de UI wordt automatisch hertekend. LaunchedEffect garandeert dat bij het opnieuw openen van het scherm (bijvoorbeeld bij terugkeren) het laden opnieuw wordt uitgevoerd als de sleutels zijn veranderd.

Voor het weergeven van laadstatussen wordt de triade van statussen gebruikt: Loading, Success, Error. LaunchedEffect wordt in try-catch verpakt, en bij succes wordt state = Success(data) ingesteld, bij fout — state = Error(exception). De UI reageert op de state en toont het bijbehorende scherm: shimmer-lader, gegevens of een foutscherm met een knop om het opnieuw te proberen.

Als gegevens moeten worden geladen tijdens het scrollen (pagination), wordt LaunchedEffect gecombineerd met LazyColumn en LazyListState: bij het bereiken van het einde van de lijst wordt de LaunchedEffect-sleutel bijgewerkt (bijvoorbeeld de paginateller), wat het laden van de volgende gegevensportie triggert.

kotlin
@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) }
    }
}

Sleutels beheren en herstarten

Correct gebruik van LaunchedEffect-sleutels — de sleutel tot effectief werken met effecten (hier weerspiegelt de woordspeling de essentie). Als de sleutel een mutable waarde is die vaak verandert (bijvoorbeeld de zoekopdrachttekst bij elk ingevoerd teken), zal elk teken de vorige coroutine annuleren en een nieuwe starten. Voor zoeken met vertraging is dit overbodig — het is beter om debounce binnen de coroutine zelf te gebruiken.

Voor het implementeren van debounce binnen LaunchedEffect gebruikt u delay() vóór het uitvoeren van de hoofdactie. Bijvoorbeeld bij zoeken: LaunchedEffect(query) wordt gestart bij elke wijziging van query, maar vóór het uitvoeren van het verzoek staat delay(500). Als de gebruiker het volgende teken eerder dan 500 ms invoert, wordt de coroutine geannuleerd (vanwege sleutelwijziging) en een nieuwe gestart — zo wordt het verzoek pas verzonden na een pauze van 500 ms bij het typen.

Een andere techniek — gebruik van sealed class als sleutel. Dit maakt nauwkeurige controle mogelijk over wanneer het effect opnieuw moet worden gestart. Bijvoorbeeld een sleutel-verpakking bevat de identificatie en een vlag voor geforceerde update: bij wijziging van de vlag van false naar true wordt LaunchedEffect herstart, zelfs als de identificatie niet is veranderd. Een dergelijk patroon is handig voor pull-to-refresh.

kotlin
// Zoeken met debounce 500ms
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// Pull-to-refresh met geforceerde update
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

Veelvoorkomende fouten met LaunchedEffect

De eerste en meest voorkomende fout — gebruik van LaunchedEffect zonder sleutels. Als u LaunchedEffect { ... } zonder argumenten schrijft, wordt de coroutine bij elke recompositie opnieuw gestart, wat leidt tot een oneindige lus van verzoeken. LaunchedEffect vereist ten minste één sleutel — meestal Unit voor eenmalige uitvoering.

De tweede fout — poging om LaunchedEffect te gebruiken voor abonneren op Flow zonder collect. Als binnen LaunchedEffect collect op Flow wordt aangeroepen, wordt de coroutine opgeschort tot de voltooiing van Flow (die in het geval van StateFlow nooit plaatsvindt), en het onDispose-blok kan niet normaal worden afgesloten. De juiste benadering — gebruik van collectLatest, die de vorige collectie annuleert bij ontvangst van een nieuwe waarde.

De derde fout — doorgeven van geneste objecten als sleutel. Als de sleutel een data class is met veranderlijke velden (var), kan LaunchedEffect de wijziging mogelijk niet herkennen, omdat Compose equals() gebruikt voor vergelijking, en dat kan voor var-velden onvoorspelbaar werken. Gebruik altijd immutable objecten (val) of primitieven als sleutels van LaunchedEffect.

Veelgestelde vragen

Wat gebeurt er als er geen sleutel aan LaunchedEffect wordt doorgegeven?

Als er geen sleutels worden doorgegeven, wordt LaunchedEffect niet gecompileerd — Kotlin vereist ten minste één argument voor vararg-parameters. Gebruik LaunchedEffect(Unit) voor eenmalige uitvoering bij binnenkomst in de compositie of geef specifieke waarden door waarbij het effect opnieuw moet worden gestart.

Kan LaunchedEffect geheugenlekken veroorzaken?

Nee, LaunchedEffect annuleert de coroutine automatisch bij het verlaten van de compositie door de composable, wat geheugenlekken voorkomt. Als de coroutine binnen LaunchedEffect echter een verwijzing naar Activity of Context vasthoudt via een closure, is een lek mogelijk — gebruik viewModelScope voor langdurige bewerkingen in ViewModel.

Wat is het verschil tussen LaunchedEffect en rememberCoroutineScope?

LaunchedEffect voert de coroutine automatisch uit bij binnenkomst in de compositie met koppeling aan sleutels. rememberCoroutineScope biedt een scope voor het handmatig starten van coroutines, bijvoorbeeld als reactie op onItemClick. Gebruik LaunchedEffect voor automatische side effects en rememberCoroutineScope voor het starten van coroutines op gebruikersgebeurtenissen.

Waarom wordt LaunchedEffect meerdere keren uitgevoerd bij recompositie?

Als de sleutel van LaunchedEffect een instabiel type is (bijvoorbeeld var of een klasse zonder equals()), kan Compose mogelijk niet herkennen dat de waarde niet is veranderd en wordt het effect bij elke recompositie opnieuw gestart. Oplossing: gebruik stabiele typen (primitieven, strings, data class met val-velden) of wikkel mutable waarden in remember.

Hoe stop ik LaunchedEffect handmatig?

Er is geen directe manier om LaunchedEffect van buitenaf te stoppen — de controle verloopt via sleutels. Wijzig de sleutel om de huidige coroutine te annuleren. Als u volledige controle over de levenscyclus van de coroutine nodig heeft, gebruik dan rememberCoroutineScope met Job en roep handmatig job.cancel() aan bij een gebeurtenis of statuswijziging.

Samenvatting

  • LaunchedEffect — Jetpack Compose side-effect API voor het starten van asynchrone coroutines die zijn gekoppeld aan de levenscyclus van composable.
  • Sleutels — herstartsysteem op basis van sleutels: sleutelwijziging annuleert de huidige coroutine en start een nieuwe met bijgewerkte parameters.
  • Auto-annulering — de coroutine wordt automatisch geannuleerd bij het verlaten van de compositie door composable, waardoor geheugenlekken worden voorkomen.
  • Gegevens laden — typisch patroon: LaunchedEffect(key) voor het laden van gegevens uit het netwerk met afhandeling van Loading, Success, Error.
  • Debounce — geïmplementeerd via delay() binnen LaunchedEffect: bij sleutelwijziging vóór het verstrijken van de vertraging wordt de coroutine geannuleerd.
  • Flow-abonnement — gebruik collectLatest in plaats van collect voor correcte verwerking van Flow binnen LaunchedEffect.
  • Stabiele sleutels — gebruik alleen immutable typen (val, primitieven, data class) als sleutels voor voorspelbaar gedrag.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook