SideEffect — ay isang composable function sa Jetpack Compose na nagpapatupad ng ipinasa na bloke ng code sa bawat matagumpay na recomposisyon. Hindi tulad ng LaunchedEffect at DisposableEffect, ang SideEffect ay hindi nakatali sa mga key at walang bloke ng paglilinis — ito ay nag-sa-sync lamang ng Compose state sa mga panlabas na sistema pagkatapos ng bawat rendering. Ginagawa nitong perpekto para sa pag-update ng mga callback function, pag-sync sa ViewPager at pagpapadala ng data sa Analytics SDK. Ayon sa Android Developers Documentation (2025), ang SideEffect ay isinasagawa nang mahigpit pagkatapos kumpirmahin ng Compose ang isang matagumpay na recomposisyon, at hindi isinasagawa kung ang recomposisyon ay nilaktawan.
Mga Pangunahing Punto
SideEffect — ay ang pinakasimpleng side-effect API sa Jetpack Compose. Ito ay nagpapatupad ng isang bloke ng code sa bawat matagumpay na recomposisyon ng isang composable component. Ang salitang “matagumpay” ay susi dito: kung nagpasya ang Compose na hindi kailangan ang recomposisyon (halimbawa, lahat ng input parameter ay hindi nagbago at ang resulta ay magiging pareho), hindi isinasagawa ang SideEffect. Tinitiyak nito na ang bloke ng pag-sync ay tinatawag lamang kapag ang UI ay aktwal na nagbago.
Ang pangunahing senaryo ng paggamit ng SideEffect — pag-sync ng Compose state sa mga object na hindi bahagi ng Compose tree. Mga karaniwang halimbawa: pag-update ng isang callback function sa Legacy View system, pagpapadala ng kasalukuyang estado sa ViewPager, pagpapadala ng event sa Analytics SDK kapag nagbago ang ipinapakitang data, pag-sync sa mga mapa SDK na umaasa ng mga update sa panlabas na format.
Ayon sa Android Developer Blog (2025), ang SideEffect ay madalas na ginagamit kasama ng remember: ang remember ay nag-iimbak ng object (halimbawa, callback), at ang SideEffect ay nag-a-update nito sa bawat pagbabago ng dependency. Ang pattern na ito ay lalong mahalaga para sa mga library na tumatanggap ng mga listener object at hindi muling ginagawa ang mga ito sa pag-update — kung walang SideEffect, ang listener ay maglalaman ng lumang reference sa kasalukuyang estado.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
Upang maunawaan ang SideEffect, kailangan mong malaman ang mga phase ng pagpapatupad ng Jetpack Compose. Ang Compose ay dumadaan sa tatlong phase para sa bawat frame: Composition (ano ang ipapakita), Layout (saan ipapakita), Drawing (paano ipapakita). Ang SideEffect ay isinasagawa sa dulo ng Composition phase — pagkatapos gumana ang lahat ng composable function, ngunit bago ang Layout phase. Tinitiyak nito na nakikita ng SideEffect ang huling estado ng lahat ng variable pagkatapos ng recomposisyon.
Ang posisyong ito sa lifecycle ay nagbibigay ng mahalagang kalamangan: ang SideEffect ay hindi maaaring magdulot ng walang-hanggang recomposisyon, kahit na magbago ang estado sa loob nito. Dahil isinasagawa ito pagkatapos ng komposisyon, ang mga pagbabagong ginawa sa loob ng SideEffect ay isasaalang-alang lamang sa susunod na frame — pinipigilan nito ang mga loop na katangian ng mga pagbabago sa loob ng body ng composable function (kapag ang setState sa loob ng komposisyon ay nag-trigger ng bagong komposisyon bago matapos ang kasalukuyan).
Isa pang feature — ang SideEffect ay hindi na-optimize ng mga key. Ito ay isinasagawa sa bawat recomposisyon anuman ang estadong nagbago. Kung kinakailangan ang mas tumpak na kontrol (isagawa lamang kapag nagbago ang isang partikular na parameter), gamitin ang LaunchedEffect na may mga key o balutin ang SideEffect sa isang pagsusuri ng pagbabago sa pamamagitan ng remember.
// SideEffect na na-optimize gamit ang remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Kung wala ang pagsusuring ito, tatawagin ng SideEffect ang animateToZoom
// sa bawat recomposisyon, kahit na hindi nagbago ang zoomLevel
Ang pinakakaraniwang praktikal na senaryo para sa SideEffect — pag-update ng mga callback function na kumukulong sa kasalukuyang estado. Sa Jetpack Compose, ito ay tinatawag na “callback lifecycle management”. Ang problema ay ang mga lambda expression sa Kotlin ay kumukuha ng mga variable sa pamamagitan ng reference, at kung ang isang callback ay ginawa na may isang halaga ng variable, at pagkatapos ay nagbago ang variable — ang callback ay patuloy na gumagamit ng lumang halaga.
Isaalang-alang ang isang halimbawa: ang Google Maps SDK para sa Android ay tumatanggap ng OnCameraMoveListener object sa pamamagitan ng setOnCameraMoveListener(). Kung magpapasa ka ng lambda na kumukuha ng isTrackingEnabled, kapag nagbago ang isTrackingEnabled, hindi mag-a-update ang lambda — ang Maps SDK ay patuloy na tatawag sa lumang callback na may lumang data. Nilulutas ng SideEffect ang problemang ito: ito ay muling nagtatakda ng listener sa bawat recomposisyon, na tinitiyak na ang SDK ay palaging gumagamit ng napapanahong lambda na may kasalukuyang estado.
Ayon sa Dokumentasyon ng Maps SDK para sa Android (2025), inirerekomenda ng Google ang eksaktong pattern na ito kapag isinasama ang Maps sa Jetpack Compose. Ang isang katulad na diskarte ay ginagamit para sa WebView, VideoView, TextureView at anumang iba pang View-based na mga component na tumatanggap ng mga callback sa pamamagitan ng set-methods. Tinitiyak ng SideEffect ang pagiging napapanahon ng mga callback sa bawat pagbabago ng estado.
@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 })
}
Isa pang mahalagang senaryo ng SideEffect — pagpapadala ng mga event sa mga analytics system kapag nagbago ang estado ng UI. Halimbawa, kapag ang user ay nagpalit ng mga tab sa TabLayout sa loob ng isang Compose screen, ang SideEffect ay maaaring magpadala ng kasalukuyang estado ng napiling tab sa Firebase Analytics o AppsFlyer. Sa bawat oras na magbago ang napiling tab (at maganap ang recomposisyon), ang SideEffect ay nagpapadala ng kaukulang event.
Ang pagkakaiba sa pagpapadala ng mga event nang direkta sa onClick o onTabSelected ay ang SideEffect ay tumutugon sa pagbabago ng estado na dulot ng anumang paraan — hindi lamang aksyon ng user, kundi pati na rin ang programmatic na pagbabago, pagpapanumbalik ng estado pagkatapos ng pag-ikot ng screen o Deep Link. Ginagawa nitong ang SideEffect ay isang unibersal na mekanismo ng pag-sync, independiyente sa pinagmulan ng pagbabago.
Ayon sa Firebase Best Practices (Google, 2025), ang pagpapadala ng mga analytics event sa pamamagitan ng SideEffect ay nagbibigay ng mas kumpletong larawan ng landas ng user, dahil naitala nito ang lahat ng pagbabago ng estado, kabilang ang mga nangyayari nang walang direktang aksyon ng user. Gayunpaman, mahalagang huwag lumampas: bawat event sa Analytics ay isang network request, kaya para sa mga madalas na nagbabagong estado (posisyon ng scroll, coordinate ng daliri) ang SideEffect ay hindi angkop — gumamit ng debounce o magpadala lamang ng mga event sa mga makabuluhang pagbabago.
@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 na may TabRow at napiling tab
}
Ang pagpili sa pagitan ng SideEffect at LaunchedEffect ay nakasalalay sa dalawang salik: kailangan ba ang asynchrony at kailangan ba ang pamamahala sa pamamagitan ng mga key. Ang SideEffect ay synchronous at isinasagawa sa bawat recomposisyon. Ang LaunchedEffect ay asynchronous (coroutine) at isinasagawa lamang kapag nagbago ang key, hindi sa bawat recomposisyon.
Kung kailangan mong magsagawa ng aksyon sa bawat pagbabago ng UI — gamitin ang SideEffect. Kung kailangan mong magsagawa ng aksyon nang isang beses kapag lumitaw ang screen o kapag nagbago ang isang partikular na parameter — gamitin ang LaunchedEffect na may mga key. Kung kinakailangan ang isang asynchronous na operasyon (pag-load ng data, pagkaantala, pagtatrabaho sa Flow) — LaunchedEffect lamang, dahil hindi sinusuportahan ng SideEffect ang mga suspend function.
| Katangian | SideEffect | LaunchedEffect |
|---|---|---|
| Pagpapatupad | Bawat recomposisyon | Kapag nagbago ang mga key |
| Asynchrony | Synchronous | Coroutine |
| Mga Key | Wala | Mayroon (vararg) |
| Paglilinis | Wala | Awtonatikong pagkansela ng coroutine |
| Karaniwang gamit | Callback, Analytics, View sync | Pag-load, Flow subscription, timer |
Sa praktika, 70% ng mga kaso ng paggamit ng side effects ay nasasakop ng LaunchedEffect (asynchronous na operasyon, pag-load ng data), 20% ng DisposableEffect (mga mapagkukunan na may paglilinis) at 10% lamang ng SideEffect (pag-sync ng mga callback function). Ang SideEffect ay isang dalubhasang tool para sa isang makitid na hanay ng mga gawain, ngunit sa mga gawaing iyon ito ay hindi mapapalitan.
Ang pangunahing pagkakamali — pagbabago ng Compose state sa loob ng SideEffect. Bagaman ang SideEffect ay hindi direktang nagdudulot ng walang-hanggang loop (dahil isinasagawa ito pagkatapos ng phase ng komposisyon), maaari itong magdulot ng labis na mga recomposisyon. Kung ang estado (mutableStateOf) ay binago sa loob ng SideEffect, ito ay nagti-trigger ng bagong recomposisyon sa susunod na frame, na muling magpapatupad ng SideEffect — at iba pa hanggang sa pag-stabilize. Hindi ito walang-hanggang loop, ngunit dagdag na trabaho para sa framework.
Ang pangalawang pagkakamali — pagsasagawa ng mabibigat na kalkulasyon sa loob ng SideEffect. Dahil ang SideEffect ay tinatawag sa bawat recomposisyon, at ang mga recomposisyon ay maaaring mangyari ng sampu-sampung beses bawat segundo (sa mga animation, scroll), anumang mabigat na code sa loob ng SideEffect ay hahantong sa pagbaba ng frame. Ilipat ang mabibigat na operasyon sa labas ng komposisyon — sa coroutine (LaunchedEffect) o kalkulahin sa pamamagitan ng derivedStateOf / remember.
Ang pangatlong pagkakamali — pagtatangkang gamitin ang SideEffect para sa asynchronous code. Ang SideEffect ay hindi isang suspend function, kaya ang delay(), await(), collect() at iba pang coroutine operations sa loob nito ay hindi magko-compile. Kung kailangan mong magsagawa ng asynchronous na aksyon pagkatapos ng recomposisyon, gamitin ang snapshotFlow { ... } sa kombinasyon sa LaunchedEffect o magpatakbo ng coroutine sa pamamagitan ng rememberCoroutineScope.
Mga Madalas Itanong
Oo, ang SideEffect ay isinasagawa sa bawat matagumpay na komposisyon, kabilang ang una — kapag ang component ay unang lumitaw sa screen. Ito ay nagpapakilala nito mula sa LaunchedEffect(Unit), na isinasagawa din nang isang beses sa unang komposisyon, ngunit hindi isinasagawa sa mga kasunod na recomposisyon (kung ang key ay hindi nagbago).
Hindi, ang SideEffect ay isinasagawa pagkatapos ng phase ng komposisyon — ang mga pagbabagong ginawa sa loob nito ay ilalapat lamang sa susunod na frame, na pumipigil sa mga loop. Gayunpaman, ang madalas na pagbabago ng estado sa loob ng SideEffect ay maaaring magdulot ng avalanche ng mga recomposisyon, na nagpapababa ng performance. Baguhin ang estado sa loob ng SideEffect lamang kapag talagang kinakailangan.
Ang SideEffect ay isinasagawa nang synchronous sa bawat recomposisyon. Ang snapshotFlow ay gumagawa ng Flow mula sa Compose state at maaaring gamitin sa collectLatest sa LaunchedEffect para sa reaktibong pagproseso ng mga pagbabago. Ang snapshotFlow ay angkop para sa mga kaso kung kailan kailangan mong tumugon sa mga pagbabago na may debounce, filter o distinctUntilChanged — na imposible sa synchronous SideEffect.
Gamitin ang Android Studio Compose Modifier Debugger o magdagdag ng logging na may pangalan ng component at dalas ng mga tawag. Kung ang SideEffect ay isinasagawa nang mas madalas kaysa sa inaasahan, suriin kung ang estado ng parent component ay nagbabago nang hindi kinakailangan. Pag-optimize: ihiwalay ang mga matatag na bahagi ng UI sa magkakahiwalay na composable function na may unstable-annotations upang mabawasan ang bilang ng mga recomposisyon.
Oo, maaari silang magamit sa isang component para sa iba't ibang layunin. Ang DisposableEffect ay responsable para sa pag-setup at paglilinis ng mapagkukunan (isang beses), at ang SideEffect — para sa pag-sync ng kasalukuyang estado sa mapagkukunang ito sa bawat recomposisyon. Isang tipikal na halimbawa: ang DisposableEffect ay nagrerehistro ng isang callback sa pamamagitan ng API, at ang SideEffect ay nag-a-update ng nakapaloob na data sa callback na ito sa bawat pagbabago.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din