Как использовать корутины в ViewModel (например, с lifecycleScope)?

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

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

Вместо использования lifecycleScope внутри ViewModel, применяют viewModelScope — это специальный CoroutineScope, привязанный к ViewModel. Он автоматически отменяется, когда ViewModel очищается (onCleared()).

Подключение зависимостей

Добавь в build.gradle:

dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
}

Что такое viewModelScope

viewModelScope — это CoroutineScope, который принадлежит ViewModel. Он запускает корутины, которые автоматически отменяются при уничтожении ViewModel. Это помогает избежать утечек памяти и проблем с отменой задач.

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

class MyViewModel : ViewModel() {
private val \_data = MutableLiveData<String>()
val data: LiveData<String> = \_data
fun loadData() {
viewModelScope.launch {
val result = fetchDataFromNetwork()
\_data.value = result
}
}
private suspend fun fetchDataFromNetwork(): String {
delay(1000) // имитация сетевого запроса
return "Готовые данные"
}
}

Объяснение:

  • viewModelScope.launch {} запускает корутину.

  • Сетевой вызов не блокирует основной поток.

  • LiveData обновляется после получения данных.

Использование с Dispatchers

По умолчанию корутины запускаются на Dispatchers.Main. Для тяжёлых задач — переключайся на Dispatchers.IO.

viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
heavyComputation()
}
\_data.value = result
}

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

Ошибки в корутинах можно ловить через try/catch:

viewModelScope.launch {
try {
val data = fetchData()
\_data.value = data
} catch (e: Exception) {
\_data.value = "Ошибка: ${e.message}"
}
}

Или с CoroutineExceptionHandler:

private val handler = CoroutineExceptionHandler { \_, exception ->
Log.e("ViewModel", "Ошибка: $exception")
}
viewModelScope.launch(handler) {
val result = fetchData()
\_data.value = result
}

Пример с Room и корутинами

class NotesViewModel(private val dao: NoteDao) : ViewModel() {
val allNotes = dao.getAllNotes().asLiveData()
fun insertNote(note: Note) {
viewModelScope.launch(Dispatchers.IO) {
dao.insert(note)
}
}
}

Пример с Retrofit

class ApiViewModel(private val api: MyApi) : ViewModel() {
private val \_response = MutableLiveData<String>()
val response: LiveData<String> = \_response
fun fetch() {
viewModelScope.launch {
try {
val res = withContext(Dispatchers.IO) { api.getData() }
\_response.value = res.body()?.data ?: "Пусто"
} catch (e: Exception) {
\_response.value = "Ошибка: ${e.message}"
}
}
}
}

Отличие от lifecycleScope

Scope Где используется Автоотмена
viewModelScope Внутри ViewModel При onCleared()
--- --- ---
lifecycleScope Внутри Activity/Fragment При onDestroy()
--- --- ---

lifecycleScope не предназначен для ViewModel, так как у него нет доступа к LifecycleOwner. Он применяется в UI-компонентах.

Использование Flow в ViewModel

class FlowViewModel : ViewModel() {
val userFlow: Flow<User> = repository.getUserStream()
val userLiveData: LiveData<User> = userFlow.asLiveData()
fun collectManually() {
viewModelScope.launch {
repository.getUserStream().collect { user ->
// что-то делаем с user
}
}
}
}

Двусторонняя привязка с UI

Во фрагменте:

viewModel.data.observe(viewLifecycleOwner) { text ->
binding.textView.text = text
}

Или с StateFlow/SharedFlow:

lifecycleScope.launchWhenStarted {
viewModel.stateFlow.collect { state ->
// обновить UI
}
}

Очистка задач при закрытии ViewModel

Корутины внутри viewModelScope автоматически отменяются, когда ViewModel уничтожается, например при смене конфигурации (ориентация экрана, выход со страницы). Поэтому тебе не нужно вручную останавливать или управлять жизненным циклом задач.