DisposableEffect, Jetpack Compose’da açık başlatma ve ardından kaynak temizliği gerektiren işlemler için tasarlanmış bir composable işlevidir. Diğer side-effect API’lerinden farklı olarak DisposableEffect, bileşen kompozisyondan çıktığında veya anahtar değiştiğinde garanti edilen bir onDispose bloğu sağlar. Bu, onu yerel abonelikler, sensör dinleyicileri ve donanım kaynaklarıyla çalışmak için vazgeçilmez kılar. Android Developers Documentation (2025)’a göre DisposableEffect, Activity yaşam döngüsündeki onStart/onStop’a benzer şekilde setup/teardown çifti gerektiren tüm senaryolarda kullanılması önerilir.
Önemli Noktalar
DisposableEffect, Jetpack Compose’da kaynak yönetimi için önemli bir araçtır. Ana özelliği, composable bileşenin yaşam döngüsü sona erdiğinde onDispose bloğunun garanti edilerek çağrılmasıdır. Bu davranış, kapatılmamış sistem hizmeti aboneliklerinin bellek sızıntılarına ve uygulama çökmelerine yol açabileceği Android geliştirme için kritiktir.
Eşzamansız bir coroutine bağlamında çalışan LaunchedEffect’in aksine DisposableEffect senkron olarak yürütülür. Bu, içinde suspend işlevleri çağıramayacağınız anlamına gelir. Senkron yürütme öngörülebilirliği garanti eder: başlatma kodunun ilk işlemeden önce ve temizlik kodunun bileşen bellekten kaldırılmadan önce yürütüleceğinden emin olabilirsiniz.
Jetpack Compose Belgeleri (2025)’ne göre DisposableEffect dört ana senaryoda kullanılmalıdır: (1) sistem hizmetlerine (sensörler, LocationManager) abone olma, (2) BroadcastReceiver kaydı, (3) coroutine’leri desteklemeyen callback tabanlı kütüphanelerle çalışma, (4) Compose bileşenlerini AndroidView aracılığıyla eski View sistemlerine bağlama.
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("Sensör: $value")
}
DisposableEffect’in iç mekaniği, kompozisyon yaşam döngüsünün aşamalarına dayanır. Bir composable bileşen kompozisyona girdiğinde, DisposableEffect iletilen kod bloğunu yürütür. Bu blok, onDispose lambda’sını içeren bir DisposableEffectResult nesnesi döndürür. Kompozisyon bu sonucu kaydeder ve bileşen kompozisyondan ayrıldığında onDispose’u çağırır — nedeni ne olursa olsun (gezinme, üst durum değişikliği, LazyColumn’dan kaldırma).
DisposableEffect’teki anahtar mekanizması LaunchedEffect’e benzer şekilde çalışır: herhangi bir anahtar değiştiğinde, önce eski durum için onDispose yürütülür, ardından başlatma bloğu yeni anahtarlarla yeniden çalıştırılır. Bu, parametreleri değiştiğinde bir kaynağı yeniden yapılandırmanıza olanak tanır. Örneğin, anahtar bir soket URL’si ise, değiştiğinde eski soket kapatılır ve yenisi açılır.
Önemli: onDispose bloğu DisposableEffect’in zorunlu bir öğesidir. Blok içinde onDispose’u çağırmazsanız, kod derlenmez. Bu derleyici gereksinimi, geliştiricinin kaynak temizliğini sağlamayı unutmamasını garanti eder; bu, manuel abonelik yönetiminde sık görülen bir hata nedenidir.
// Bir anahtarla doğru kullanım
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Tek DisposableEffect’te birden çok kaynak
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Android uygulamalarındaki bellek sızıntıları genellikle, ekran kapatıldıktan sonra bir Activity veya Context’e referans tutmaya devam eden kayıtlı olmayan dinleyiciler ve abonelikler nedeniyle oluşur. DisposableEffect bu sorunu framework düzeyinde çözer: geliştirici bir dinleyiciyi kaydetmek için DisposableEffect kullanırsa, onDispose herhangi bir bileşen sonlandırma senaryosunda aboneliği iptal etmeyi garanti eder.
Bu, özellikle kullanıcı kaydırdıkça öğelerin sürekli oluşturulup yok edildiği LazyColumn ve LazyGrid için kritiktir. DisposableEffect olmadan, görünür alandan kaybolan her öğe aktif bir abonelik bırakır. DisposableEffect ile, kaldırılan her öğe için onDispose çağrılır ve öğe ekrandan ayrılır ayrılmaz kaynakların serbest bırakılması sağlanır.
Android Performance Patterns (Google, 2025)’a göre, tüm yerel abonelikler için DisposableEffect kullanımı, yaşam döngüsü callback’leri aracılığıyla manuel yönetime kıyasla Compose uygulamalarındaki bellek sızıntılarını %60–70 oranında azaltır. Sistem, bileşenin kompozisyondan ayrılma anını kendisi izler ve acil ekran kapatmada bile onDispose yürütülmesini garanti eder.
| Kaynak | DisposableEffect’in Yaptığı | DisposableEffect Olmadan |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Alıcı aktif kalır |
| SensorManager | registerListener + onDispose → unregisterListener | Sensör veri göndermeye devam eder |
| Observable (Flow değil) | subscribe + onDispose → unsubscribe | Callback referansı tutar |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Callback sızıntısı |
| Socket / Channel | open + onDispose → close | Bağlantı açık kalır |
DisposableEffect kullanımının en açıklayıcı örneklerinden biri, cihaz sensörleriyle (ivmeölçer, jiroskop, manyetometre) çalışmaktır. Sensörler, iş tamamlandığında zorunlu kayıt iptali gerektirir; aksi takdirde ekran kapatıldıktan sonra bile pil gücü tüketmeye ve veri göndermeye devam ederler.
Pratik örnek: eğim açısı ölçüm uygulaması. DisposableEffect(Unit), bileşen göründüğünde bir ivmeölçer dinleyicisi kaydeder ve onDispose’ta kaydı iptal eder. Sensör verileri mutableStateOf aracılığıyla duruma iletilir ve bu da arayüzü otomatik olarak günceller. Ekran LazyColumn’da kaydırılırsa ve öğe kaybolursa, onDispose hemen tetiklenir — sensör o öğe için veri göndermeyi durdurur.
Sensör türü değiştirildiğinde (örneğin, ivmeölçerden jiroskopa), sensorType anahtarı değişir, onDispose eski aboneliği iptal eder ve yeni DisposableEffect bloğu yeni sensörü kaydeder. Anahtarlar olmadan, hangi sensörün daha önce kaydedildiğini manuel olarak kontrol etmeniz ve doğru dinleyiciyle unregisterListener çağırmanız gerekir — bu da hataya açıktır.
@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("Değer: $sensorValue")
}
BroadcastReceiver, zorunlu bir register / unregister çifti gerektiren bir API’nin klasik bir örneğidir. Bir Compose uygulamasında DisposableEffect, belirli bir ekranın ömrü boyunca bir alıcıyı kaydetmek için idealdir. Ekrana girildiğinde, gerekli IntentFilter ile bir BroadcastReceiver kaydedilir; çıkıldığında, onDispose’ta otomatik olarak iptal edilir.
Tipik bir senaryo ağ durumu izlemedir. DisposableEffect, ağ bağlantısı değişikliklerini bildiren ConnectivityManager için bir alıcı kaydeder. Durum değiştiğinde (WiFi / mobil veri / ağ yok), composable durumu güncellenir ve arayüz ilgili göstergeyi görüntüler. Ekran kapatıldığında, onDispose kaydın iptal edilmesini garanti eder — uygulama arka plana gönderilse bile.
RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED bayrağıyla (Android 14+) ContextCompat.registerReceiver kullanan alıcılar için DisposableEffect kullanımı zorunlu hale gelir çünkü sistem, alıcının kapsamının açıkça belirtilmesini gerektirir. DisposableEffect, kapsamın ekranın ömrüyle sınırlı olmasını sağlar ve bu, daha yeni Android sürümlerinin güvenlik gereksinimleriyle uyumludur.
@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) { ... }
}
İlk kritik hata onDispose çağrısının eksikliğidir. DisposableEffect bloğu içindeki kod onDispose’u çağırmalıdır, aksi takdirde derleme hatası oluşur. Ancak geliştiriciler bazen onDispose’u bir koşula koyarak bunu atlatmaya çalışır: if (condition) { onDispose { ... } }. Bu kod derlenecektir, ancak koşul karşılanmazsa onDispose kaydedilmez — kaynak asla serbest bırakılmaz.
İkinci hata DisposableEffect’i eşzamansız işlemler için kullanmaktır. DisposableEffect senkron olduğundan, içinde delay() veya await() çağrıları yazamazsınız. Ardından temizlik ile eşzamansız başlatma gerekiyorsa, LaunchedEffect (veri yükleme için) ve DisposableEffect (yerel kaynakları kurma/temizleme için) kombinasyonunu veya rememberCoroutineScope ile ayrı bir mekanizma kullanın.
Üçüncü hata remember olmadan DisposableEffect içinde yeni nesneler oluşturmaktır. Nesneler (sensör, dinleyici, alıcı) her çağrıda efekt içinde oluşturulur ve anahtarlar sık sık değişirse, bu aşırı nesne oluşturmaya ve çöp toplamaya yol açar. Nesne oluşturmayı DisposableEffect dışında remember veya remember { ... } içine taşımak ve efekt içinde yalnızca kaydetme ve iptal etme işlemlerini yapmak daha iyidir.
Sıkça Sorulan Sorular
DisposableEffect senkron çalışır ve açık kaynak temizliği için onDispose sağlar. LaunchedEffect bir coroutine içinde eşzamansız çalışır ve anahtar değiştiğinde veya kompozisyondan çıkıldığında onu otomatik olarak iptal eder. Bir kaynak bir temizlik yöntemi (close, unregister, dispose) çağrısı gerektiriyorsa — DisposableEffect kullanın. İşlem bir suspend işleviyse — LaunchedEffect kullanın.
Evet, onDispose zorunludur — Kotlin derleyicisi, DisposableEffect bloğu içinde çağrılmasını gerektirir. onDispose’u çağırmazsanız, kod derlenmez. Bu, geliştiricilerin unutmasını önlemek ve açılan her kaynağın kompozisyondan çıkarken düzgün bir şekilde kapatılmasını sağlamak için kasıtlı olarak yapılmıştır.
DisposableEffect bloğu içinde try-catch kullanın. Kaynak kaydı bir istisna fırlatabiliyorsa (örneğin, sensör bulunamadı), try ile sarın ve ayrı bir durum aracılığıyla arayüzde hatayı ele alın. onDispose, başlatmanın başarısından bağımsız olarak çağrılmalıdır — bir finally bloğuna veya try bölümünün sonuna koyun.
Önerilmez. Flow için, collectLatest ile LaunchedEffect veya Lifecycle.repeatOnLifecycle ile .collectAsState() yöntemini kullanmak daha iyidir. DisposableEffect suspend işlevlerini desteklemez, bu nedenle içinde Flow’a abone olmak, CoroutineScope aracılığıyla ayrı bir coroutine başlatmayı gerektirir, bu da kodu karmaşıklaştırır ve sızıntı riskini artırır.
Herhangi bir sınırlama yoktur, ancak ilgili kaynakların içinde birden çok işlem ve tek bir onDispose ile tek bir DisposableEffect’te gruplandırılması önerilir. Kaynaklar bağımsızsa (örneğin, sensör ve BroadcastReceiver), bunları farklı anahtarlarla ayrı DisposableEffect’lere bölmek daha iyidir — bu, hata ayıklamayı basitleştirir ve bir anahtar değiştiğinde tüm kaynakların istenmeyen şekilde yeniden oluşturulmasını önler.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun