DisposableEffect ay isang composable-function sa Jetpack Compose na idinisenyo para sa mga operasyon na nangangailangan ng malinaw na pagsisimula at kasunod na paglilinis ng mga resources. Hindi tulad ng iba pang side-effect API, ang DisposableEffect ay nagbibigay ng onDispose block na garantadong ma-e-execute kapag ang component ay lumabas sa komposisyon o kapag nagbago ang key. Ginagawa nitong kailangang-kailangan para sa pagtatrabaho sa mga native na subscription, sensor listener, at hardware resources. Ayon sa Android Developers Documentation (2025), ang DisposableEffect ay inirerekomenda sa lahat ng scenario kung saan kinakailangan ang setup/teardown na pares, katulad ng onStart/onStop sa lifecycle ng Activity.
Mga Pangunahing Punto
DisposableEffect ay isang pangunahing kasangkapan para sa pamamahala ng resources sa Jetpack Compose. Ang pangunahing tampok nito ay ang garantadong tawag ng onDispose block sa pagtatapos ng lifecycle ng composable-component. Ang pag-uugaling ito ay kritikal para sa pag-develop ng Android, kung saan ang mga hindi saradong subscription sa mga serbisyo ng system ay maaaring humantong sa memory leaks at pag-crash ng application.
Hindi tulad ng LaunchedEffect, na gumagana sa asynchronous na konteksto ng coroutine, ang DisposableEffect ay nag-e-execute nang synchronous. Ibig sabihin nito ay hindi maaaring tawagin ang mga suspend-function sa loob nito. Ang synchronisidad ay nagsisiguro ng predictability: maaari kang maging sigurado na ang initialization code ay ma-e-execute bago ang unang render, at ang cleanup code — bago ang component ay alisin mula sa memorya.
Ayon sa Dokumentasyon ng Jetpack Compose (2025), ang DisposableEffect ay dapat gamitin sa apat na pangunahing scenario: (1) pag-subscribe sa mga serbisyo ng system (sensors, LocationManager), (2) pagrehistro ng BroadcastReceiver, (3) pagtatrabaho sa mga callback-based na library na hindi sumusuporta sa coroutine, (4) pag-uugnay ng mga Compose component sa Legacy View system sa pamamagitan ng AndroidView.
class SensorManager(private val context: Context) {
fun startListening(callback: (Float) -> Unit) { /* register */ }
fun stopListening() { /* cancel */ }
}
@Composable
fun SensorDisplay() {
val sensorManager = remember { SensorManager(context) }
var value by remember { mutableStateOf(0f) }
DisposableEffect(Unit) {
sensorManager.startListening { value = it }
onDispose { sensorManager.stopListening() }
}
Text("Sensor: $value")
}
Ang panloob na mekanismo ng DisposableEffect ay batay sa mga phase ng lifecycle ng komposisyon. Kapag ang isang composable-component ay pumasok sa komposisyon, ang DisposableEffect ay tumatawag sa ibinigay na block ng code. Ang block na ito ay nagbabalik ng isang DisposableEffectResult object na naglalaman ng onDispose lambda. Ang komposisyon ay nag-iimbak ng resultang ito at tinatawagan ang onDispose sa sandaling ang component ay umalis sa komposisyon — anuman ang dahilan (navigation, pagbabago ng estado ng magulang, pag-alis mula sa LazyColumn).
Ang mekanismo ng key sa DisposableEffect ay gumagana katulad ng LaunchedEffect: kapag may anumang key na nagbago, una munang nag-e-execute ang onDispose para sa lumang estado, pagkatapos ang initialization block ay muling sinisimulan gamit ang mga bagong key. Ito ay nagpapahintulot sa muling pag-configure ng resource kapag nagbago ang mga parameter nito. Halimbawa, kung ang key ay ang URL ng socket, kapag nagbago ito, ang lumang socket ay sarado at isang bagong socket ay bubuksan.
Mahalaga: ang onDispose block ay isang mandatoryong elemento ng DisposableEffect. Kung hindi tatawagin ang onDispose sa loob ng block, ang code ay hindi mag-compile. Ang pangangailangang ito ng compiler ay nagsisiguro na ang developer ay hindi makakalimot na magbigay ng paglilinis ng resource, na isang karaniwang sanhi ng mga error sa manu-manong pamamahala ng mga subscription.
// Tamang paggamit na may key
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Maramihang resources sa isang DisposableEffect
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Ang memory leaks sa mga Android application ay madalas na nangyayari dahil sa mga hindi naka-rehistrong listener at subscription na patuloy na humahawak ng reference sa Activity o Context pagkatapos na sarado ang screen. DisposableEffect ay nilulutas ang problemang ito sa antas ng framework: kung ang developer ay gumamit ng DisposableEffect para sa pagrehistro ng listener, garantadong kakanselahin ng onDispose ang subscription sa anumang scenario ng pagtatapos ng component.
Ito ay lalong kritikal para sa LazyColumn at LazyGrid, kung saan ang mga elemento ay patuloy na nililikha at sinisira habang nag-i-scroll. Kung walang DisposableEffect, bawat elemento na mawala sa view ay mag-iiwan ng aktibong subscription. Sa DisposableEffect, ang onDispose ay tinatawagan para sa bawat na-unload na elemento, na nagsisiguro na ang mga resources ay agad na napapalaya pagkatapos na ang elemento ay umalis sa screen.
Ayon sa Android Performance Patterns (Google, 2025), ang paggamit ng DisposableEffect para sa lahat ng native na subscription ay nagbabawas ng bilang ng memory leaks sa Compose application ng 60–70% kumpara sa manu-manong pamamahala sa pamamagitan ng lifecycle callbacks. Ang sistema mismo ay sumusubaybay sa sandali ng pag-alis mula sa komposisyon at ginagarantiyahan ang pag-execute ng onDispose kahit sa emergency na pagsasara ng screen.
| Resource | Ano ang ginagawa ng DisposableEffect | Walang DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receiver ay nananatiling aktibo |
| SensorManager | registerListener + onDispose → unregister | Sensor ay patuloy na nagpapadala ng data |
| Observable (hindi Flow) | subscribe + onDispose → unsubscribe | Callback ay humahawak ng reference |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Callback leak |
| Socket / Channel | open + onDispose → close | Koneksyon ay nananatiling bukas |
Isa sa mga pinakamahusay na halimbawa ng paggamit ng DisposableEffect — pagtatrabaho sa mga sensor ng device (accelerometer, gyroscope, magnetometer). Ang mga sensor ay nangangailangan ng mandatoryong pagkansela ng rehistrasyon sa pagtatapos ng trabaho, kung hindi man ay patuloy silang kumokonsumo ng enerhiya ng baterya at nagpapadala ng data kahit na matapos na sarado ang screen.
Praktikal na halimbawa: application para sa pagsukat ng anggulo ng pagkahilig. Ang DisposableEffect(Unit) ay nagrerehistro ng accelerometer listener kapag lumitaw ang component at kinakansela ang rehistrasyon sa onDispose. Ang data ng sensor ay ipinapasa sa estado sa pamamagitan ng mutableStateOf, na awtomatikong nag-a-update ng UI. Kung ang screen ay na-scroll sa LazyColumn at ang elemento ay nawala, ang onDispose ay agad na na-activate — ang sensor ay humihinto sa pagpapadala ng data para sa elementong ito.
Kapag nagbago ang uri ng sensor (halimbawa, mula accelerometer patungong gyroscope), ang sensorType key ay nagbabago, kinakansela ng onDispose ang lumang subscription, at ang bagong DisposableEffect block ay nagrerehistro ng bagong sensor. Kung walang mga key, kakailanganing manual na suriin kung aling sensor ang dating naka-rehistro at tawagan ang unregisterListener na may tamang listener — na madaling magkamali.
@Composable
fun SensorReadingScreen(sensorType: Int) {
val context = LocalContext.current
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
var sensorValue by remember { mutableStateOf(0f) }
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
val listener = SensorEventListener { event, _ ->
sensorValue = event.values[0]
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
Text("Halaga: $sensorValue")
}
Ang BroadcastReceiver ay isang klasikong halimbawa ng API na nangangailangan ng mandatoryong pares na register / unregister. Sa Compose application, ang DisposableEffect ay perpekto para sa pagrehistro ng receiver para sa habang-buhay ng isang partikular na screen. Sa pagpasok sa screen, ang BroadcastReceiver ay nirerehistro na may tamang IntentFilter, sa paglabas — awtomatikong kinakansela sa onDispose.
Karaniwang scenario — pagmonitor ng status ng network. Ang DisposableEffect ay nagrerehistro ng receiver sa ConnectivityManager na nag-aabiso tungkol sa mga pagbabago sa koneksyon ng network. Kapag nagbago ang status (WiFi / mobile data / walang network) ang estado ng composable ay na-a-update, at ang UI ay nagpapakita ng kaukulang indicator. Kapag ang screen ay sarado, garantadong kinakansela ng onDispose ang rehistrasyon — kahit na ang application ay pumunta sa background.
Para sa mga receiver na may ContextCompat.registerReceiver at flag na RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) ang paggamit ng DisposableEffect ay nagiging mandatoryo, dahil ang sistema ay nangangailangan ng malinaw na pagtukoy ng domain ng pagkilos ng receiver. Ginagarantiyahan ng DisposableEffect na ang domain ng pagkilos ay limitado sa habang-buhay ng screen, na naaayon sa mga kinakailangan sa seguridad ng mga bagong bersyon ng Android.
@Composable
fun NetworkStatusBanner() {
val context = LocalContext.current
var isConnected by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
val receiver = BroadcastReceiver { _, _ ->
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
isConnected = cm.getActiveNetwork() != null
}
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
context.registerReceiver(receiver, filter)
}
onDispose {
context.unregisterReceiver(receiver)
}
}
if (!isConnected) { ... }
}
Ang unang kritikal na pagkakamali — kawalan ng tawag sa onDispose. Ang code sa loob ng DisposableEffect block ay dapat tumawag ng onDispose, kung hindi ay magkakaroon ng compilation error. Gayunpaman, minsan sinusubukan ng mga developer na iwasan ito sa pamamagitan ng paglalagay ng onDispose sa isang condition: if (condition) { onDispose { ... } }. Ang ganitong code ay mag-compile, ngunit ang onDispose ay hindi mare-rehistro kung hindi matugunan ang condition — ang resource ay hindi kailanman mapapalaya.
Ang pangalawang pagkakamali — paggamit ng DisposableEffect para sa asynchronous na operasyon. Dahil ang DisposableEffect ay synchronous, hindi maaaring isulat ang mga tawag na delay() o await() sa loob nito. Kung kinakailangan ang asynchronous na pagsisimula na may kasunod na paglilinis, gamitin ang kombinasyon ng LaunchedEffect (para sa pag-load ng data) at DisposableEffect (para sa pag-setup/paglilinis ng native na resources), o gumamit ng hiwalay na mekanismo na may rememberCoroutineScope.
Ang pangatlong pagkakamali — paggawa ng mga bagong object sa loob ng DisposableEffect nang walang remember. Kung sa loob ng effect ay gumagawa ng mga object (sensor, listener, receiver) sa bawat tawag, at ang mga key ay madalas nagbabago, ito ay humahantong sa labis na paggawa ng object at garbage collection. Mas mainam na ilipat ang paggawa ng mga object sa remember o remember { ... } sa labas ng DisposableEffect, at sa loob ng effect ay magrehistro at kumansela lamang.
Mga Madalas Itanong
DisposableEffect ay gumagana nang synchronous at nagbibigay ng onDispose para sa malinaw na paglilinis ng resources. LaunchedEffect ay gumagana nang asynchronous sa isang coroutine at awtomatikong kinakansela ito kapag nagbago ang key o lumabas sa komposisyon. Kung ang resource ay nangangailangan ng pagtawag ng cleanup method (close, unregister, dispose) — gamitin ang DisposableEffect. Kung ang operasyon ay isang suspend-function — gamitin ang LaunchedEffect.
Oo, ang onDispose ay mandatoryo — ang Kotlin compiler ay nangangailangan ng pagtawag nito sa loob ng DisposableEffect block. Kung hindi tatawagin ang onDispose, ang code ay hindi mag-compile. Ito ay sadyang idinisenyo upang maiwasan ang pagkalimot ng mga developer at masiguro na ang bawat bukas na resource ay isasara kapag lumabas sa komposisyon.
Gumamit ng try-catch sa loob ng DisposableEffect block. Kung ang rehistrasyon ng resource ay maaaring mag-throw ng exception (halimbawa, hindi natagpuan ang sensor), balutin ito sa try at hawakan ang error sa UI sa pamamagitan ng hiwalay na estado. Ang onDispose ay dapat tawagin anuman ang tagumpay ng pagsisimula — ilagay ito sa finally block o sa dulo ng try section.
Hindi inirerekomenda. Para sa Flow mas mainam na gamitin ang LaunchedEffect na may collectLatest o ang .collectAsState() method na may Lifecycle.repeatOnLifecycle. Ang DisposableEffect ay hindi sumusuporta sa mga suspend-function, kaya ang pag-subscribe sa Flow sa loob nito ay mangangailangan ng pagsisimula ng hiwalay na coroutine sa pamamagitan ng CoroutineScope, na nagpapakumplikado ng code at nagpapataas ng panganib ng leaks.
Walang limitasyon, ngunit inirerekomenda na pagsamahin ang mga kaugnay na resources sa isang DisposableEffect na may maraming operasyon sa loob at isang onDispose. Kung ang mga resources ay independyente (halimbawa, sensor at BroadcastReceiver), mas mainam na hatiin ang mga ito sa magkakahiwalay na DisposableEffect na may magkakaibang key — pinapasimple nito ang debugging at pinipigilan ang hindi gustong muling paggawa ng lahat ng resources kapag nagbago ang isang key.
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