SideEffect — är en composable-funktion i Jetpack Compose som kör ett skickat kodblock vid varje lyckad recomposition. Till skillnad från LaunchedEffect och DisposableEffect är SideEffect inte bunden till nycklar och har inget rensningsblock — det synkroniserar helt enkelt Compose-tillståndet med externa system efter varje rendering. Detta gör det idealiskt för att uppdatera callback-funktioner, synkronisera med ViewPager och överföra data till Analytics SDK. Enligt Android Developers Documentation (2025) körs SideEffect strikt efter att Compose har bekräftat en lyckad recomposition och körs inte om recompositionen hoppades över.
Huvudpunkter
SideEffect — är det enklaste side-effect API:et i Jetpack Compose. Det kör ett kodblock vid varje lyckad recomposition av en composable-komponent. Ordet ”lyckad” är nyckeln här: om Compose beslutar att recomposition inte är nödvändig (till exempel om alla indataparametrar inte har ändrats och resultatet blir detsamma), körs inte SideEffect. Detta garanterar att synkroniseringsblocket endast anropas när UI faktiskt har ändrats.
Huvudscenariot för användning av SideEffect — synkronisering av Compose-tillstånd med objekt som inte är en del av Compose-trädet. Typiska exempel: uppdatering av en callback-funktion i Legacy View-systemet, överföring av aktuellt tillstånd till ViewPager, sändning av en händelse till Analytics SDK när visade data ändras, synkronisering med kart-SDK som förväntar sig uppdateringar i externt format.
Enligt Android Developer Blog (2025) används SideEffect ofta i kombination med remember: remember lagrar ett objekt (till exempel en callback) och SideEffect uppdaterar det vid varje beroendeförändring. Detta mönster är särskilt viktigt för bibliotek som accepterar listener-objekt och inte återskapar dem vid uppdatering — utan SideEffect skulle listener innehålla en föråldrad referens till det aktuella tillståndet.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
För att förstå SideEffect måste du känna till Jetpack Compose exekveringsfaser. Compose går igenom tre faser för varje bildruta: Composition (vad som ska visas), Layout (var det ska visas), Drawing (hur det ska visas). SideEffect körs i slutet av Composition-fasen — efter att alla composable-funktioner har körts, men före Layout-fasen. Detta garanterar att SideEffect ser det slutliga tillståndet för alla variabler efter recomposition.
Denna position i livscykeln ger en viktig fördel: SideEffect kan inte orsaka oändlig recomposition, även om tillståndet ändras inuti det. Eftersom det körs efter komposition kommer ändringar som görs inuti SideEffect endast att beaktas i nästa bildruta — detta förhindrar loopar som är karakteristiska för ändringar i kroppen av en composable-funktion (när setState inuti komposition utlöser en ny komposition innan den nuvarande är klar).
En annan egenskap — SideEffect optimeras inte av nycklar. Det körs vid varje recomposition oavsett vilket tillstånd som har ändrats. Om mer precis kontroll krävs (kör endast vid ändring av en specifik parameter), använd LaunchedEffect med nycklar eller linda in SideEffect i en ändringskontroll via remember.
// SideEffect optimerad med remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Utan denna kontroll skulle SideEffect anropa animateToZoom
// vid varje recomposition, även om zoomLevel inte ändrades
Det vanligaste praktiska scenariot för SideEffect — uppdatering av callback-funktioner som omsluter aktuellt tillstånd. I Jetpack Compose kallas detta ”hantering av callback-livscykel”. Problemet är att lambda-uttryck i Kotlin fångar variabler via referens, och om en callback skapades med ett variabelvärde och sedan variabeln ändrades — fortsätter callback att använda det gamla värdet.
Betrakta ett exempel: Google Maps SDK för Android accepterar OnCameraMoveListener-objektet via setOnCameraMoveListener(). Om du skickar en lambda som fångar isTrackingEnabled, då när isTrackingEnabled ändras, uppdateras inte lambdan — Maps SDK fortsätter att anropa den gamla callbacken med föråldrade data. SideEffect löser detta problem: den återställer listener vid varje recomposition, vilket garanterar att SDK alltid använder den aktuella lambdan med det aktuella tillståndet.
Enligt Dokumentationen för Maps SDK för Android (2025) rekommenderar Google precis detta mönster vid integration av Maps med Jetpack Compose. Ett liknande tillvägagångssätt används för WebView, VideoView, TextureView och andra View-baserade komponenter som accepterar callbacks via set-metoder. SideEffect garanterar att callbacks är aktuella vid varje tillståndsförändring.
@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 })
}
Ett annat viktigt scenario för SideEffect — sändning av händelser till analyssystem när UI-tillståndet ändras. Till exempel, när användaren växlar flikar i TabLayout inom en Compose-skärm, kan SideEffect överföra det aktuella tillståndet för den valda fliken till Firebase Analytics eller AppsFlyer. Varje gång den valda fliken ändras (och recomposition sker), skickar SideEffect motsvarande händelse.
Skillnaden från att skicka händelser direkt i onClick eller onTabSelected är att SideEffect reagerar på tillståndsförändringar som orsakats på vilket sätt som helst — inte bara användaråtgärd, utan även programmatisk ändring, återställning av tillstånd efter skärmrotation eller Deep Link. Detta gör SideEffect till en universell synkroniseringsmekanism, oberoende av ändringskällan.
Enligt Firebase Best Practices (Google, 2025) ger sändning av analyshändelser via SideEffect en mer komplett bild av användarens väg, eftersom den registrerar alla tillståndsförändringar, inklusive de som sker utan direkt användaråtgärd. Det är dock viktigt att inte överdriva: varje händelse i Analytics är en nätverksbegäran, så för ofta föränderliga tillstånd (scrollposition, fingerkoordinater) är SideEffect inte lämplig — använd debounce eller skicka händelser endast vid betydande förändringar.
@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 med TabRow och vald flik
}
Valet mellan SideEffect och LaunchedEffect beror på två faktorer: behövs asynkronicitet och behövs hantering via nycklar. SideEffect är synkront och körs vid varje recomposition. LaunchedEffect är asynkront (korutin) och körs endast när nyckeln ändras, inte vid varje recomposition.
Om du behöver utföra en åtgärd vid varje UI-ändring — använd SideEffect. Om du behöver utföra en åtgärd en gång när skärmen visas eller när en specifik parameter ändras — använd LaunchedEffect med nycklar. Om en asynkron operation krävs (dataladdning, fördröjning, arbete med Flow) — endast LaunchedEffect, eftersom SideEffect inte stöder suspend-funktioner.
| Egenskap | SideEffect | LaunchedEffect |
|---|---|---|
| Exekvering | Varje recomposition | När nycklar ändras |
| Asynkronicitet | Synkront | Korutin |
| Nycklar | Nej | Ja (vararg) |
| Rensning | Nej | Automatisk annulering av korutin |
| Typisk användning | Callbacks, Analytics, View-synkronisering | Laddning, Flow-prenumeration, timers |
I praktiken täcks 70% av användningsfallen av side effects av LaunchedEffect (asynkrona operationer, dataladdning), 20% av DisposableEffect (resurser med rensning) och endast 10% av SideEffect (synkronisering av callback-funktioner). SideEffect är ett specialiserat verktyg för en snäv uppgiftskrets, men i dessa uppgifter är det oersättligt.
Huvudmisstaget — att ändra Compose-tillstånd inuti SideEffect. Även om SideEffect inte direkt orsakar en oändlig loop (eftersom den körs efter kompositionsfasen), kan den orsaka överdrivna recompositioner. Om tillståndet (mutableStateOf) ändras inuti SideEffect, utlöser detta en ny recomposition i nästa bildruta, som igen kör SideEffect — och så vidare tills stabilisering. Detta är inte en oändlig loop, men extra arbete för ramverket.
Andra misstaget — att utföra tunga beräkningar inuti SideEffect. Eftersom SideEffect anropas vid varje recomposition och recompositioner kan ske tiotals gånger per sekund (vid animeringar, scrollning), kommer all tung kod inuti SideEffect att leda till bildruteförluster. Flytta tunga operationer utanför kompositionen — till en korutin (LaunchedEffect) eller beräkna via derivedStateOf / remember.
Tredje misstaget — att försöka använda SideEffect för asynkron kod. SideEffect är inte en suspend-funktion, så delay(), await(), collect() och andra korutinoperationer inuti den kommer inte att kompileras. Om du behöver utföra en asynkron åtgärd efter recomposition, använd snapshotFlow { ... } i kombination med LaunchedEffect eller starta en korutin via rememberCoroutineScope.
Vanliga frågor
Ja, SideEffect körs vid varje lyckad komposition, inklusive den första — när komponenten först visas på skärmen. Detta skiljer det från LaunchedEffect(Unit), som också körs en gång vid första kompositionen, men inte körs vid efterföljande recompositioner (om nyckeln inte har ändrats).
Nej, SideEffect körs efter kompositionsfasen — ändringar som görs inuti det kommer endast att tillämpas i nästa bildruta, vilket förhindrar loopar. Dock kan frekvent tillståndsförändring inuti SideEffect orsaka en lavin av recompositioner, vilket minskar prestandan. Ändra tillstånd inuti SideEffect endast när det verkligen behövs.
SideEffect körs synkront vid varje recomposition. snapshotFlow skapar ett Flow från Compose-tillståndet och kan användas med collectLatest i LaunchedEffect för reaktiv bearbetning av ändringar. snapshotFlow är lämpligt för fall där du behöver reagera på ändringar med debounce, filter eller distinctUntilChanged — vilket är omöjligt i synkront SideEffect.
Använd Android Studio Compose Modifier Debugger eller lägg till loggning med komponentnamn och anropsfrekvens. Om SideEffect körs oftare än förväntat, kontrollera om tillståndet för föräldrakomponenten ändras i onödan. Optimering: isolera stabila delar av UI i separata composable-funktioner med unstable-annoteringar för att minska antalet recompositioner.
Ja, de kan användas i samma komponent för olika ändamål. DisposableEffect ansvarar för inställning och rensning av en resurs (en gång), och SideEffect — för synkronisering av aktuellt tillstånd med denna resurs vid varje recomposition. Ett typiskt exempel: DisposableEffect registrerar en callback via API, och SideEffect uppdaterar de omslutna data i denna callback vid varje förändring.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också