← Назад к вопросам

Для чего нужен DisposableEffect?

2.3 Middle🔥 161 комментариев
#UI и вёрстка#Жизненный цикл и навигация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

DisposableEffect в Jetpack Compose

DisposableEffect — это композиционный хук (side-effect) в Jetpack Compose, который выполняет очистку (cleanup) когда композиция переходит в другое состояние. Это аналог useEffect с cleanup функцией в React.

Проблема: Управление ресурсами

Без DisposableEffect (неправильно)

@Composable
fun LocationScreen(locationManager: LocationManager) {
    // ❌ Проблема: listener добавляется каждый раз при перекомпозиции
    locationManager.startListening { location ->
        // Обновляем UI
    }
    
    // Но listener НИКОГДА не удаляется!
    // Memory leak: множественные listeners скапливаются
}

Решение: DisposableEffect

@Composable
fun LocationScreen(locationManager: LocationManager) {
    DisposableEffect(locationManager) {
        // Эта блок выполняется при входе в композицию
        val listener = { location: Location ->
            println("Location: $location")
        }
        locationManager.startListening(listener)
        
        // onDispose блок выполняется при выходе из композиции
        onDispose {
            locationManager.stopListening(listener)
            println("Listener удалён!")
        }
    }
}

Жизненный цикл DisposableEffect

@Composable
fun EffectDemo() {
    println("1. Начало композиции")
    
    DisposableEffect(Unit) {
        println("2. DisposableEffect выполняется")
        
        // Можно здесь выделить ресурсы
        val resource = acquireResource()
        
        onDispose {
            println("5. onDispose вызван!")
            // Очистить ресурсы
            resource.release()
        }
    }
    
    println("3. Остальная композиция")
}

// Вывод:
// 1. Начало композиции
// 2. DisposableEffect выполняется
// 3. Остальная композиция
// [при выходе из композиции]
// 5. onDispose вызван!

Keys: Когда пересчитывать

@Composable
fun ChatScreen(userId: String) {
    DisposableEffect(userId) {  // ← Key!
        println("Подключаемся к пользователю: $userId")
        val connection = chatService.connect(userId)
        
        onDispose {
            println("Отключаемся от пользователя: $userId")
            connection.close()
        }
    }
}

// Если userId изменится (например, с "user123" на "user456"):
// 1. Вызывается onDispose для старого userId
// 2. Затем заново выполняется блок с новым userId

Практические примеры

1. Подписка на события жизненного цикла

@Composable
fun LifecycleAwareScreen() {
    val lifecycleOwner = LocalLifecycleOwner.current
    
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> println("Screen visible")
                Lifecycle.Event.ON_PAUSE -> println("Screen hidden")
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

2. Управление сенсорами (GPS, акселерометр)

@Composable
fun AccelerometerScreen(sensorManager: SensorManager) {
    DisposableEffect(Unit) {
        val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        val listener = object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent) {
                val x = event.values[0]
                val y = event.values[1]
                val z = event.values[2]
                println("Acceleration: X=$x, Y=$y, Z=$z")
            }
            
            override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
        }
        
        sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL)
        
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }
}

3. Управление WebSocket соединением

@Composable
fun ChatScreen(roomId: String, webSocketManager: WebSocketManager) {
    DisposableEffect(roomId) {
        println("Подключаемся к room: $roomId")
        val connection = webSocketManager.connect(roomId)
        
        connection.onMessageReceived { message ->
            println("Сообщение: $message")
        }
        
        onDispose {
            println("Закрываем соединение с room: $roomId")
            connection.close()
        }
    }
}

4. Управление таймерами

@Composable
fun TimerScreen() {
    DisposableEffect(Unit) {
        var elapsed = 0
        val timer = Timer()
        
        timer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                elapsed++
                println("Прошло: $elapsed сек")
            }
        }, 0, 1000)  // Каждую секунду
        
        onDispose {
            timer.cancel()  // Остановить таймер!
        }
    }
}

5. Управление AppCompatDelegate для темы

@Composable
fun ThemeAwareScreen(isDarkMode: Boolean) {
    DisposableEffect(isDarkMode) {
        val previousMode = AppCompatDelegate.getDefaultNightMode()
        
        val newMode = if (isDarkMode) {
            AppCompatDelegate.MODE_NIGHT_YES
        } else {
            AppCompatDelegate.MODE_NIGHT_NO
        }
        
        AppCompatDelegate.setDefaultNightMode(newMode)
        
        onDispose {
            // Вернуть предыдущий режим
            AppCompatDelegate.setDefaultNightMode(previousMode)
        }
    }
}

6. Регистрация BroadcastReceiver

@Composable
fun BatteryStatusScreen(context: Context) {
    DisposableEffect(Unit) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) ?: 0
                println("Батарея: $level%")
            }
        }
        
        val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
        context.registerReceiver(receiver, filter)
        
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }
}

DisposableEffect vs LaunchedEffect

LaunchedEffect — для корутин

@Composable
fun DataFetch(userId: String) {
    LaunchedEffect(userId) {
        // Запускается в viewModelScope
        val user = repository.getUser(userId)
        // Автоматически отменяется при изменении userId
    }
}

DisposableEffect — для всего остального

@Composable
fun EventListener(eventBus: EventBus) {
    DisposableEffect(eventBus) {
        val listener = { event ->
            println("Event: $event")
        }
        eventBus.subscribe(listener)
        
        onDispose {
            eventBus.unsubscribe(listener)
        }
    }
}

Best Practices

// 1. Всегда указывай keys!
// ❌ Плохо
DisposableEffect(Unit) {  // Только один раз!
    // ...
}

// ✅ Хорошо
DisposableEffect(userId) {  // Пересчитает при изменении userId
    // ...
}

// 2. Всегда очищай ресурсы в onDispose
DisposableEffect(Unit) {
    val resource = acquireResource()
    onDispose {
        resource.close()  // ОБЯЗАТЕЛЬНО!
    }
}

// 3. Помни про memory leaks
DisposableEffect(Unit) {
    listeners.add(myListener)  // Добавили listener
    onDispose {
        listeners.remove(myListener)  // УДАЛИ его!
    }
}

// 4. Обработка исключений
DisposableEffect(Unit) {
    try {
        resource.initialize()
    } catch (e: Exception) {
        Log.e("DisposableEffect", "Ошибка", e)
    }
    onDispose {
        try {
            resource.cleanup()
        } catch (e: Exception) {
            Log.e("DisposableEffect", "Ошибка cleanup", e)
        }
    }
}

Сравнение с обычным Lifecycle

АспектDisposableEffectLifecycle Observer
УдобствоInline в composableОтдельный класс
Реакция на измененияПо keysПо событиям
ОчисткаonDispose блокonDestroy
Memory leaksПредотвращеноНужна осторожность
Compose✅ Встроено❌ Старший API

Вывод: DisposableEffect — это essential хук для управления ресурсами в Compose. Всегда используй его для подписок, слушателей и других ресурсов, требующих очистки.