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

Как запустить корутину во ViewModel

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

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

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

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

Запуск корутин во ViewModel

Корутины в ViewModel — это правильный способ управления асинхронными операциями с учётом жизненного цикла приложения. Android архитектура компоненты предоставляют для этого специальный механизм.

1. ViewModelScope

Самый правильный способ — использовать viewModelScope, который автоматически отменяет все корутины при уничтожении ViewModel:

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun loadData() {
        viewModelScope.launch {
            try {
                val data = repository.fetchData()
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
}

Когда ViewModel удаляется из памяти, viewModelScope автоматически вызывает cancel() для всех запущенных корутин.

2. Выбор правильного диспетчера

Для разных задач используются разные диспетчеры:

viewModelScope.launch(Dispatchers.Main) {
    // Обновление UI
}

viewModelScope.launch(Dispatchers.IO) {
    // Работа с БД, сетью, файлами
    val data = repository.loadFromDatabase()
}

viewModelScope.launch(Dispatchers.Default) {
    // Тяжёлые вычисления
    val result = computeExpensiveOperation()
}

3. Лучший паттерн: UseCase + Repository

Не стоит запускать корутины сразу в ViewModel. Лучше использовать use case:

// UseCase
class GetUserUseCase(private val userRepository: UserRepository) {
    suspend operator fun invoke(userId: String): User {
        return userRepository.getUser(userId)
    }
}

// ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
    private val getUserUseCase: GetUserUseCase
) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun loadUser(userId: String) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = getUserUseCase(userId)
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

4. Обработка ошибок

Корутины требуют правильной обработки исключений:

viewModelScope.launch(exceptionHandler) {
    // Код который может выбросить исключение
}

// или

viewModelScope.launch(Dispatchers.IO + exceptionHandler) {
    val result = repository.fetchData()
}

private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    println("Caught: $exception")
    _uiState.value = UiState.Error(exception.message)
}

5. Использование Flow для реактивности

Для постоянного получения обновлений данных используется Flow:

class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    val userFlow: Flow<User> = userRepository.getUserFlow()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Lazily,
            initialValue = null
        )
}

6. Отмена корутины при нужном условии

Иногда нужно отменить корутину вручную:

class DataViewModel : ViewModel() {
    private var loadJob: Job? = null
    
    fun loadData() {
        loadJob = viewModelScope.launch {
            val data = repository.fetchData()
        }
    }
    
    fun cancelLoading() {
        loadJob?.cancel()
    }
}

7. SupervisorJob для независимых корутин

Если нужно, чтобы отказ одной корутины не влиял на другие:

viewModelScope.launch(SupervisorJob()) {
    // Несколько параллельных операций
    val data1 = async { repository.fetchData1() }
    val data2 = async { repository.fetchData2() }
    
    // Если data1 упадёт, data2 продолжит работу
}

8. Timeout для долгих операций

import kotlinx.coroutines.withTimeoutOrNull

viewModelScope.launch {
    val result = withTimeoutOrNull(5000) {
        repository.fetchData()
    }
    if (result != null) {
        _uiState.value = UiState.Success(result)
    } else {
        _uiState.value = UiState.Error("Request timeout")
    }
}

9. Избежание утечек памяти

Правильная работа с ViewModel:

// ❌ Неправильно — корутина может остаться в памяти
val globalScope = GlobalScope.launch { }

// ✅ Правильно — отменяется при уничтожении ViewModel
viewModelScope.launch { }

Best Practices

  • Всегда используйте viewModelScope, никогда GlobalScope
  • Обрабатывайте ошибки с помощью CoroutineExceptionHandler или try-catch
  • Выбирайте правильный диспетчер для задачи
  • Используйте Flow для реактивных данных
  • Не забывайте про отмену длительных операций
  • Тестируйте асинхронный код с помощью TestDispatchers