SideEffect — wat is het, toestandssynchronisatie in Jetpack Compose

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

SideEffect — is een composable functie in Jetpack Compose die een doorgegeven codeblok uitvoert bij elke succesvolle recompositie. In tegenstelling tot LaunchedEffect en DisposableEffect is SideEffect niet gebonden aan sleutels en heeft het geen opschoonblok — het synchroniseert gewoon de Compose-toestand met externe systemen na elke rendering. Dit maakt het ideaal voor het bijwerken van callback-functies, synchronisatie met ViewPager en het doorgeven van gegevens aan Analytics SDK. Volgens Android Developers Documentation (2025) wordt SideEffect strikt uitgevoerd nadat Compose een succesvolle recompositie heeft bevestigd en wordt niet uitgevoerd als de recompositie is overgeslagen.

Belangrijkste punten

  • SideEffect — side-effect API voor code die wordt uitgevoerd na elke succesvolle recompositie.
  • Synchronisatie — draagt Compose-toestand over naar externe systemen die Compose niet ondersteunen.
  • Zonder sleutels — in tegenstelling tot LaunchedEffect wordt SideEffect niet herstart, maar uitgevoerd bij elke recompositie.
  • Geen opschoning — SideEffect biedt geen onDispose, het is alleen bedoeld voor eenrichtingssynchronisatie.
  • Synchroon — het blok wordt synchroon uitgevoerd met de compositiefase van Compose, zonder coroutines.

Wat is SideEffect in Jetpack Compose

SideEffect — is de eenvoudigste van de side-effect API's in Jetpack Compose. Het voert een codeblok uit bij elke succesvolle recompositie van een composable component. Het woord “succesvolle” is hier de sleutel: als Compose besluit dat recompositie niet nodig is (bijvoorbeeld als alle invoerparameters niet zijn veranderd en het resultaat hetzelfde zal zijn), wordt SideEffect niet uitgevoerd. Dit garandeert dat het synchronisatieblok alleen wordt aangeroepen wanneer de UI daadwerkelijk is veranderd.

Het belangrijkste gebruiksscenario van SideEffect — synchronisatie van de Compose-toestand met objecten die geen deel uitmaken van de Compose-boom. Typische voorbeelden: het bijwerken van een callback-functie in het Legacy View-systeem, het doorgeven van de huidige toestand aan ViewPager, het verzenden van een gebeurtenis naar Analytics SDK bij het wijzigen van weergegeven gegevens, synchronisatie met kaart-SDK's die updates in een extern formaat verwachten.

Volgens Android Developer Blog (2025) wordt SideEffect vaak gebruikt in combinatie met remember: remember bewaart een object (bijvoorbeeld een callback) en SideEffect werkt het bij bij elke verandering van afhankelijkheid. Dit patroon is vooral belangrijk voor bibliotheken die listener-objecten accepteren en deze niet opnieuw aanmaken bij een update — zonder SideEffect zou de listener een verouderde verwijzing naar de huidige toestand bevatten.

kotlin
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setZoom(zoomLevel)
        mapView.updateMarkers(markers)
    }
    
    AndroidView(factory = { mapView })
}

Hoe werkt SideEffect en de compositiefasen

Om SideEffect te begrijpen, moet je de uitvoeringsfasen van Jetpack Compose kennen. Compose doorloopt drie fasen voor elk frame: Composition (wat weer te geven), Layout (waar weer te geven), Drawing (hoe weer te geven). SideEffect wordt uitgevoerd aan het einde van de Composition-fase — nadat alle composable functies hebben gewerkt, maar vóór de Layout-fase. Dit garandeert dat SideEffect de uiteindelijke toestand van alle variabelen na recompositie ziet.

Deze positie in de levenscyclus geeft een belangrijk voordeel: SideEffect kan geen oneindige recompositie veroorzaken, zelfs als de toestand erin verandert. Omdat het na compositie wordt uitgevoerd, worden wijzigingen die in SideEffect zijn aangebracht pas in het volgende frame in aanmerking genomen — dit voorkomt lussen die kenmerkend zijn voor wijzigingen in de body van een composable functie (wanneer setState binnen compositie een nieuwe compositie triggert voordat de huidige is voltooid).

Nog een kenmerk — SideEffect wordt niet geoptimaliseerd door sleutels. Het wordt uitgevoerd bij elke recompositie, ongeacht welke toestand is veranderd. Als er nauwkeurigere controle nodig is (alleen uitvoeren bij verandering van een specifieke parameter), gebruik dan LaunchedEffect met sleutels of wikkel SideEffect in een veranderingscontrole via remember.

kotlin
// SideEffect geoptimaliseerd met remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }

SideEffect {
    if (currentZoom != zoomLevel) {
        map.animateToZoom(zoomLevel)
        currentZoom = zoomLevel
    }
}

// Zonder deze controle zou SideEffect animateToZoom aanroepen
// bij elke recompositie, zelfs als zoomLevel niet was veranderd

Callback-functies bijwerken via SideEffect

Het meest voorkomende praktijk scenario voor SideEffect — het bijwerken van callback-functies die de huidige toestand omsluiten. In Jetpack Compose wordt dit “callback lifecycle management” genoemd. Het probleem is dat lambda-expressies in Kotlin variabelen by reference vastleggen, en als een callback is gemaakt met een waarde van een variabele en de variabele verandert daarna — blijft de callback de oude waarde gebruiken.

Beschouw een voorbeeld: Google Maps SDK voor Android accepteert het OnCameraMoveListener object via setOnCameraMoveListener(). Als je een lambda doorgeeft die isTrackingEnabled vasthoudt, wordt de lambda niet bijgewerkt wanneer isTrackingEnabled verandert — Maps SDK blijft de oude callback met verouderde gegevens aanroepen. SideEffect lost dit probleem op: het stelt de listener opnieuw in bij elke recompositie, waardoor wordt gegarandeerd dat de SDK altijd de actuele lambda met de huidige toestand gebruikt.

Volgens de Documentatie van Maps SDK voor Android (2025) beveelt Google precies dit patroon aan bij integratie van Maps met Jetpack Compose. Een vergelijkbare aanpak wordt gebruikt voor WebView, VideoView, TextureView en alle andere View-gebaseerde componenten die callbacks accepteren via set-methoden. SideEffect garandeert de actualiteit van callbacks bij elke toestandsverandering.

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

Synchronisatie met Analytics SDK

Nog een belangrijk scenario van SideEffect — het verzenden van gebeurtenissen naar analysesystemen bij verandering van de UI-toestand. Bijvoorbeeld, wanneer de gebruiker tabbladen wisselt in TabLayout binnen een Compose-scherm, kan SideEffect de huidige toestand van het geselecteerde tabblad doorgeven aan Firebase Analytics of AppsFlyer. Elke keer dat het geselecteerde tabblad verandert (en recompositie plaatsvindt), verzendt SideEffect de bijbehorende gebeurtenis.

Het verschil met het rechtstreeks verzenden van gebeurtenissen in onClick of onTabSelected is dat SideEffect reageert op toestandsveranderingen die op elke manier zijn veroorzaakt — niet alleen door gebruikersactie, maar ook door programmatische wijziging, herstel van toestand na schermrotatie of Deep Link. Dit maakt SideEffect een universeel synchronisatiemechanisme, onafhankelijk van de bron van de verandering.

Volgens Firebase Best Practices (Google, 2025) geeft het verzenden van analytische gebeurtenissen via SideEffect een vollediger beeld van het gebruikerspad, omdat het alle toestandsveranderingen registreert, inclusief die welke plaatsvinden zonder directe gebruikersactie. Het is echter belangrijk om niet te overdrijven: elke gebeurtenis in Analytics is een netwerkverzoek, dus voor frequent veranderende toestanden (scrollpositie, vingercoördinaten) is SideEffect niet geschikt — gebruik debounce of stuur gebeurtenissen alleen bij significante veranderingen.

kotlin
@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 met TabRow en geselecteerd tabblad
}

SideEffect vs LaunchedEffect: wanneer gebruik je wat

De keuze tussen SideEffect en LaunchedEffect hangt af van twee factoren: is asynchroniteit nodig en is beheer via sleutels nodig. SideEffect is synchroon en wordt uitgevoerd bij elke recompositie. LaunchedEffect is asynchroon (coroutine) en wordt alleen uitgevoerd bij verandering van de sleutel, niet bij elke recompositie.

Als je een actie moet uitvoeren bij elke UI-verandering — gebruik SideEffect. Als je een actie eenmalig moet uitvoeren bij het verschijnen van het scherm of bij verandering van een specifieke parameter — gebruik LaunchedEffect met sleutels. Als een asynchrone bewerking nodig is (gegevens laden, vertraging, werken met Flow) — alleen LaunchedEffect, omdat SideEffect geen suspend-functies ondersteunt.

KenmerkSideEffectLaunchedEffect
UitvoeringBij elke recompositieBij verandering van sleutels
AsynchroniteitSynchroonCoroutine
SleutelsNeeJa (vararg)
OpschoningNeeAutomatische annulering van coroutine
Typisch gebruikCallbacks, Analytics, View-synchronisatieLaden, Flow-abonnement, timers

In de praktijk wordt 70% van de use cases van side effects gedekt door LaunchedEffect (asynchrone bewerkingen, gegevens laden), 20% door DisposableEffect (bronnen met opschoning) en slechts 10% door SideEffect (synchronisatie van callback-functies). SideEffect is een gespecialiseerd hulpmiddel voor een smalle reeks taken, maar in die taken is het onmisbaar.

Veelvoorkomende fouten met SideEffect

De belangrijkste fout — het wijzigen van Compose-toestand binnen SideEffect. Hoewel SideEffect niet direct een oneindige lus veroorzaakt (omdat het na de compositiefase wordt uitgevoerd), kan het overmatige recomposities veroorzaken. Als de toestand (mutableStateOf) binnen SideEffect wordt gewijzigd, triggert dit een nieuwe recompositie in het volgende frame, die SideEffect opnieuw uitvoert — en zo verder tot stabilisatie. Dit is geen oneindige lus, maar wel extra werk voor het framework.

De tweede fout — het uitvoeren van zware berekeningen binnen SideEffect. Omdat SideEffect wordt aangeroepen bij elke recompositie en recomposities tientallen keren per seconde kunnen plaatsvinden (bij animaties, scrollen), zal elke zware code binnen SideEffect leiden tot frameverlies. Verplaats zware bewerkingen buiten de compositie — naar een coroutine (LaunchedEffect) of bereken via derivedStateOf / remember.

De derde fout — proberen SideEffect te gebruiken voor asynchrone code. SideEffect is geen suspend-functie, dus delay(), await(), collect() en andere coroutine-bewerkingen erin zullen niet compileren. Als je een asynchrone actie moet uitvoeren na recompositie, gebruik dan snapshotFlow { ... } in combinatie met LaunchedEffect of start een coroutine via rememberCoroutineScope.

Veelgestelde vragen

Wordt SideEffect uitgevoerd bij de eerste compositie?

Ja, SideEffect wordt uitgevoerd bij elke succesvolle compositie, inclusief de eerste — wanneer het component voor het eerst op het scherm verschijnt. Dit onderscheidt het van LaunchedEffect(Unit), dat ook eenmalig wordt uitgevoerd bij de eerste compositie, maar niet bij volgende recomposities (als de sleutel niet is veranderd).

Kan SideEffect een oneindige lus veroorzaken?

Nee, SideEffect wordt uitgevoerd na de compositiefase — wijzigingen die erin worden aangebracht, worden pas in het volgende frame toegepast, wat lussen voorkomt. Echter, frequente toestandswijzigingen binnen SideEffect kunnen een lawine van recomposities veroorzaken, wat de prestaties vermindert. Wijzig de toestand binnen SideEffect alleen wanneer het echt nodig is.

Wat is het verschil tussen SideEffect en snapshotFlow?

SideEffect wordt synchroon uitgevoerd bij elke recompositie. snapshotFlow maakt een Flow van de Compose-toestand en kan worden gebruikt met collectLatest in LaunchedEffect voor reactieve verwerking van veranderingen. snapshotFlow is geschikt voor gevallen waarin je moet reageren op veranderingen met debounce, filter of distinctUntilChanged — wat onmogelijk is in synchrone SideEffect.

Hoe debug je SideEffect als het te vaak wordt uitgevoerd?

Gebruik Android Studio Compose Modifier Debugger of voeg logboekregistratie toe met de naam van het component en de frequentie van aanroepen. Als SideEffect vaker wordt uitgevoerd dan verwacht, controleer dan of de toestand van het bovenliggende component onnodig verandert. Optimalisatie: isoleer stabiele delen van de UI in afzonderlijke composable functies met unstable-annotaties om het aantal recomposities te verminderen.

Kan SideEffect worden gecombineerd met DisposableEffect?

Ja, ze kunnen in hetzelfde component voor verschillende doeleinden worden gebruikt. DisposableEffect is verantwoordelijk voor het instellen en opschonen van een bron (eenmalig), en SideEffect voor de synchronisatie van de huidige toestand met deze bron bij elke recompositie. Een typisch voorbeeld: DisposableEffect registreert een callback via de API en SideEffect werkt de omsloten gegevens in deze callback bij bij elke verandering.

Samenvatting

  • SideEffect — side-effect API voor synchrone code die wordt uitgevoerd na elke succesvolle recompositie in Jetpack Compose.
  • Synchronisatie — het belangrijkste scenario: het doorgeven van Compose-toestand aan externe systemen (Google Maps, WebView, ViewPager, Analytics SDK).
  • Callbacks — SideEffect garandeert de actualiteit van callback-functies die de huidige toestand omsluiten bij elke UI-update.
  • Zonder lussen — wordt uitgevoerd na de compositiefase, dus toestandswijzigingen binnen SideEffect veroorzaken geen oneindige recompositie.
  • Beperkingen — ondersteunt geen sleutels, asynchroniteit en opschoonblok; gebruik voor deze taken LaunchedEffect of DisposableEffect.
  • Prestaties — vermijd zware berekeningen binnen SideEffect, omdat het bij elke recompositie wordt uitgevoerd (tot 60 keer per seconde).
  • Debuggen — controleer de aanroepfrequentie via Compose Debugger en optimaliseer via remember voor het filteren van onnodige recomposities.

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