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

Какой CoroutineScope во ViewModel?

2.0 Middle🔥 221 комментариев
#Android компоненты#Многопоточность и асинхронность

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Корректный выбор CoroutineScope в ViewModel

При работе с корутинами в архитектуре Android, особенно в компонентах, связанных с UI (таких как Activity, Fragment или ViewModel), критически важно правильно выбирать и управлять CoroutineScope, чтобы избежать утечек памяти, неожиданных поведений и обеспечить корректную работу приложения.

Основной подход: использование viewModelScope

В ViewModel стандартным и рекомендуемым способом является использование предопределенного свойства viewModelScope.

viewModelScope — это расширение (extension property), предоставляемое библиотекой androidx.lifecycle:lifecycle-viewmodel-ktx. Его ключевые особенности:

  • Привязка к жизненному циклу ViewModel: Scope автоматически отменяется (cancelled) при очистке ViewModel (т.е. когда ViewModel.onCleared() вызывается). Это гарантирует, что все запущенные в этой области корутины будут остановлены, предотвращая утечки ресурсов и выполнение работы для уже неактивного ViewModel.
  • Использование Dispatchers.Main как default: Основной диспетчер этого scope — Dispatchers.Main (или Main.immediate), что удобно для операций, связанных с UI (например, обновление LiveData или StateFlow). Для тяжелых или блокирующих операций (сеть, база данных, вычисления) внутри корутины viewModelScope следует использовать withContext для переключения на другой диспетчер, например Dispatchers.IO.
  • Простота и безопасность: Использование viewModelScope исключает необходимость самостоятельно создавать и управлять scope, реализовывать onCleared() для его отмены.

Пример использования viewModelScope

class MyViewModel(private val repository: DataRepository) : ViewModel() {

    // Использование viewModelScope для запуска корутин
    fun loadData() {
        viewModelScope.launch {
            // Операции на Main диспетчере (например, обновление состояния)
            _uiState.value = UiState.Loading

            try {
                // Переключаемся на IO для сетевой или долгой операции
                val data = withContext(Dispatchers.IO) {
                    repository.fetchData()
                }
                // Возвращаемся на Main для обновления UI
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }

    // StateFlow для передачи состояния UI (часто используется с viewModelScope)
    private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}

Почему не следует использовать другие Scope в ViewModel?

  • GlobalScope (или подобные): Запуск корутин в GlobalScope или в scope, не привязанный к жизненному циклу ViewModel, является антипаттерном. Эти корутины имеют жизненный цикл приложения и будут продолжать выполняться даже после уничтожения ViewModel и связанного с ним UI, приводя к утечке памяти и потенциально некорректной работе (например, попытке обновить несуществующий View).
  • Самодельный Scope: Создание своего собственного CoroutineScope (например, private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())) и его отмена в onCleared() является допустимым, но менее удобным и более подверженным ошибкам подходом, чем использование готового viewModelScope.
// НЕ РЕКОМЕНДУЕМЫЙ подход (ручное управление)
class MyViewModelOldWay : ViewModel() {
    // Создаем собственный scope
    private val customScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

    override fun onCleared() {
        super.onCleared()
        // Обязательно нужно отменить
        customScope.cancel()
    }

    fun loadData() {
        customScope.launch { /* ... */ } // Используем собственный scope
    }
}

Итог и рекомендации

  1. Всегда используйте viewModelScope для запуска корутин внутри ViewModel. Это стандарт, принятый в Android Jetpack.
  2. viewModelScope автоматически управляет жизненным циклом корутин, отменяя их при очистке ViewModel.
  3. Для IO-операций или тяжелых вычислений внутри корутины viewModelScope используйте withContext(Dispatchers.IO) или другие соответствующие диспетчеры.
  4. Избегайте GlobalScope и самодельных scope без четкой привязки к жизненному циклу компонента в UI-слое приложения.

Таким образом, viewModelScope является не просто техническим выбором, а важной частью архитектурного паттерна, обеспечивающего жизненный цикл, безопасность памяти и консистентность состояния в Android приложениях, построенных на корутинах и ViewModel.