SideEffect — egy composable függvény a Jetpack Compose-ban, amely minden sikeres recompozíciónál végrehajtja a megadott kódblokkot. A LaunchedEffect és DisposableEffect függvényekkel ellentétben a SideEffect nincs kulcsokhoz kötve, és nincs tisztítási blokkja — egyszerűen szinkronizálja a Compose-állapotot külső rendszerekkel minden renderelés után. Ez ideálissá teszi callback függvények frissítésére, ViewPager-rel való szinkronizációra és adatok Analytics SDK-ba történő továbbítására. A Android Developers Documentation (2025) szerint a SideEffect szigorúan azután hajtódik végre, hogy a Compose megerősítette a sikeres recompozíciót, és nem hajtódik végre, ha a recompozíció kimaradt.
Főbb pontok
SideEffect — a legegyszerűbb side-effect API a Jetpack Compose-ban. Végrehajt egy kódblokkot egy composable komponens minden sikeres recompozíciójánál. A „sikeres” szó itt kulcsfontosságú: ha a Compose úgy dönt, hogy a recompozíció nem szükséges (például az összes bemeneti paraméter nem változott, és az eredmény ugyanaz lesz), a SideEffect nem hajtódik végre. Ez garantálja, hogy a szinkronizációs blokk csak akkor hívódik meg, amikor a UI ténylegesen megváltozott.
A SideEffect fő használati forgatókönyve — a Compose-állapot szinkronizálása olyan objektumokkal, amelyek nem részei a Compose-fának. Tipikus példák: callback függvény frissítése a Legacy View rendszerben, aktuális állapot továbbítása a ViewPager-be, esemény küldése az Analytics SDK-nak a megjelenített adatok változásakor, szinkronizáció térkép SDK-kkal, amelyek külső formátumban várják a frissítéseket.
A Android Developer Blog (2025) szerint a SideEffect gyakran használatos a remember függvénnyel kombinálva: a remember tárol egy objektumot (például callback-et), a SideEffect pedig frissíti azt minden függőségi változáskor. Ez a minta különösen fontos azoknál a könyvtáraknál, amelyek listener objektumokat fogadnak el, és nem hozzák létre újra azokat frissítéskor — SideEffect nélkül a listener elavult hivatkozást tartalmazna az aktuális állapotra.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
A SideEffect megértéséhez ismerni kell a Jetpack Compose végrehajtási fázisait. A Compose három fázison megy keresztül minden képkockánál: Composition (mit jelenítsen meg), Layout (hol jelenítse meg), Drawing (hogyan jelenítse meg). A SideEffect a Composition fázis végén hajtódik végre — miután az összes composable függvény lefutott, de a Layout fázis előtt. Ez garantálja, hogy a SideEffect a recompozíció után az összes változó végső állapotát látja.
Ez az elhelyezkedés az életciklusban fontos előnyt biztosít: a SideEffect nem okozhat végtelen recompozíciót, még akkor sem, ha az állapot megváltozik benne. Mivel a kompozíció után hajtódik végre, a SideEffect-en belül végrehajtott változtatások csak a következő képkockában kerülnek figyelembevételre — ez megakadályozza a composable függvény törzsében bekövetkező változásokra jellemző hurkokat (amikor a setState a kompozíción belül új kompozíciót indít el a jelenlegi befejezése előtt).
Egy másik jellemző — a SideEffect nem optimalizálható kulcsokkal. Minden recompozíciónál végrehajtódik, függetlenül attól, hogy melyik állapot változott meg. Ha pontosabb vezérlésre van szükség (csak egy adott paraméter változásakor hajtódjon végre), használjon LaunchedEffect-et kulcsokkal, vagy csomagolja be a SideEffect-et egy remember-en keresztüli változásellenőrzésbe.
// SideEffect optimalizálva remember-rel
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Ezen ellenőrzés nélkül a SideEffect meghívná az animateToZoom-ot
// minden recompozíciónál, még akkor is, ha a zoomLevel nem változott
A SideEffect leggyakoribb gyakorlati forgatókönyve — az aktuális állapotot lezáró callback függvények frissítése. Ezt a Jetpack Compose-ban “callback lifecycle management”-nek nevezik. A probléma az, hogy a Kotlin lambda kifejezései a változókat referenciával fogják be, és ha egy callback egy változó egyik értékével jött létre, majd a változó megváltozott — a callback továbbra is a régi értéket használja.
Tekintsünk egy példát: a Google Maps SDK Androidhoz az OnCameraMoveListener objektumot a setOnCameraMoveListener() segítségével fogadja el. Ha átad egy lambdát, amely bekapja az isTrackingEnabled értéket, akkor amikor az isTrackingEnabled megváltozik, a lambda nem frissül — a Maps SDK továbbra is a régi callback-et hívja elavult adatokkal. A SideEffect megoldja ezt a problémát: minden recompozíciónál újra beállítja a listenert, garantálva, hogy az SDK mindig a friss lambdát használja az aktuális állapottal.
A Maps SDK dokumentációja Androidhoz (2025) szerint a Google pontosan ezt a mintát ajánlja a Maps Jetpack Compose-hoz való integrálásakor. Hasonló megközelítést használnak a WebView, VideoView, TextureView és bármely más View-alapú komponens esetében, amelyek set-metódusokon keresztül fogadnak callback-eket. A SideEffect garantálja a callback-ek aktualitását minden állapotváltozáskor.
@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 })
}
A SideEffect másik fontos forgatókönyve — események küldése analitikai rendszerekbe a UI állapotának változásakor. Például amikor a felhasználó füleket vált a TabLayout-ban egy Compose-képernyőn belül, a SideEffect továbbíthatja a kiválasztott fül aktuális állapotát a Firebase Analytics vagy AppsFlyer felé. Minden alkalommal, amikor a kiválasztott fül megváltozik (és recompozíció történik), a SideEffect elküldi a megfelelő eseményt.
A különbség az események közvetlen onClick vagy onTabSelected belüli küldéséhez képest az, hogy a SideEffect bármilyen módon okozott állapotváltozásra reagál — nemcsak a felhasználói műveletre, hanem programozott változtatásra, állapotvisszaállításra képernyőelforgatás után vagy Deep Link-re is. Ez teszi a SideEffect-et univerzális szinkronizációs mechanizmussá, amely független a változás forrásától.
A Firebase Best Practices (Google, 2025) szerint az analitikai események SideEffect-en keresztüli küldése teljesebb képet ad a felhasználói útról, mivel rögzíti az összes állapotváltozást, beleértve azokat is, amelyek közvetlen felhasználói beavatkozás nélkül történnek. Fontos azonban nem túlzásba esni: minden esemény az Analytics-ben hálózati kérés, ezért a gyakran változó állapotokhoz (görgetési pozíció, ujj koordináták) a SideEffect nem megfelelő — használjon debounce-t, vagy csak jelentős változásoknál küldjön eseményeket.
@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 TabRow-val és kiválasztott füllel
}
A SideEffect és LaunchedEffect közötti választás két tényezőtől függ: szükség van-e aszinkronitásra, és szükség van-e kulcsokkal történő vezérlésre. A SideEffect szinkron és minden recompozíciónál végrehajtódik. A LaunchedEffect aszinkron (coroutine) és csak a kulcs megváltozásakor hajtódik végre, nem minden recompozíciónál.
Ha minden UI-változáskor végre kell hajtani egy műveletet — használja a SideEffect-et. Ha egy műveletet egyszer kell végrehajtani a képernyő megjelenésekor vagy egy adott paraméter változásakor — használja a LaunchedEffect-et kulcsokkal. Ha aszinkron műveletre van szükség (adatok betöltése, késleltetés, Flow-val való munka) — csak LaunchedEffect, mert a SideEffect nem támogatja a suspend függvényeket.
| Jellemző | SideEffect | LaunchedEffect |
|---|---|---|
| Végrehajtás | Minden recompozíciónál | Kulcsok változásakor |
| Aszinkronitás | Szinkron | Coroutine |
| Kulcsok | Nincs | Van (vararg) |
| Tisztítás | Nincs | Coroutine automatikus megszakítása |
| Tipikus használat | Callback-ek, Analytics, View szinkronizáció | Betöltés, Flow feliratkozás, időzítők |
A gyakorlatban a side effects használatának 70%-át a LaunchedEffect (aszinkron műveletek, adatbetöltés), 20%-át a DisposableEffect (tisztítással járó erőforrások) és csak 10%-át a SideEffect (callback függvények szinkronizációja) fedi le. A SideEffect egy speciális eszköz a feladatok szűk köréhez, de ezekben a feladatokban pótolhatatlan.
A fő hiba — a Compose-állapot megváltoztatása a SideEffect-en belül. Bár a SideEffect nem okoz közvetlenül végtelen hurkot (mivel a kompozíciós fázis után hajtódik végre), túlzott recompozíciókat okozhat. Ha az állapot (mutableStateOf) megváltozik a SideEffect-en belül, ez új recompozíciót indít el a következő képkockában, amely újra végrehajtja a SideEffect-et — és így tovább a stabilizálódásig. Ez nem végtelen hurok, de extra munka a keretrendszer számára.
A második hiba — nehéz számítások végrehajtása a SideEffect-en belül. Mivel a SideEffect minden recompozíciónál meghívódik, és a recompozíciók másodpercenként több tucatszor is megtörténhetnek (animációknál, görgetésnél), bármilyen nehéz kód a SideEffect-en belül képkocka-kieséshez vezet. Vigye ki a nehéz műveleteket a kompozícióból — coroutine-ba (LaunchedEffect) vagy számítsa ki derivedStateOf / remember segítségével.
A harmadik hiba — a SideEffect használatának kísérlete aszinkron kódhoz. A SideEffect nem suspend függvény, ezért a delay(), await(), collect() és más coroutine műveletek benne nem fordulnak le. Ha aszinkron műveletet kell végrehajtani recompozíció után, használja a snapshotFlow { ... } kombinációját a LaunchedEffect-tel, vagy indítson coroutine-t a rememberCoroutineScope segítségével.
Gyakran Ismételt Kérdések
Igen, a SideEffect minden sikeres kompozíciónál végrehajtódik, beleértve az elsőt is — amikor a komponens először jelenik meg a képernyőn. Ez különbözteti meg a LaunchedEffect(Unit)-tól, amely szintén egyszer végrehajtódik az első kompozíciónál, de a későbbi recompozícióknál nem (ha a kulcs nem változott).
Nem, a SideEffect a kompozíciós fázis után hajtódik végre — a benne végrehajtott változtatások csak a következő képkockában kerülnek alkalmazásra, ami megakadályozza a hurkokat. Az állapot gyakori megváltoztatása a SideEffect-en belül azonban recompozíciók lavináját okozhatja, csökkentve a teljesítményt. Csak akkor változtassa meg az állapotot a SideEffect-en belül, ha valóban szükséges.
A SideEffect szinkron módon hajtódik végre minden recompozíciónál. A snapshotFlow Flow-t hoz létre a Compose-állapotból, és használható a collectLatest-tel a LaunchedEffect-ben a változások reaktív feldolgozásához. A snapshotFlow azokra az esetekre alkalmas, amikor debounce, filter vagy distinctUntilChanged segítségével kell reagálni a változásokra — ami a szinkron SideEffect-ben lehetetlen.
Használja az Android Studio Compose Modifier Debugger-t, vagy adjon hozzá naplózást a komponens nevével és a hívások gyakoriságával. Ha a SideEffect gyakrabban hajtódik végre a vártnál, ellenőrizze, hogy a szülő komponens állapota nem változik-e szükségtelenül. Optimalizálás: különítse el a UI stabil részeit külön composable függvényekbe unstable annotációkkal a recompozíciók számának csökkentése érdekében.
Igen, használhatók ugyanabban a komponensben különböző célokra. A DisposableEffect felelős az erőforrás beállításáért és tisztításáért (egyszer), a SideEffect pedig az aktuális állapot ezzel az erőforrással való szinkronizálásáért minden recompozíciónál. Tipikus példa: a DisposableEffect regisztrál egy callback-et az API-n keresztül, a SideEffect pedig frissíti a bezárt adatokat ebben a callback-ben minden változáskor.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is