Как использовать корутины в 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 уничтожается, например при смене конфигурации (ориентация экрана, выход со страницы). Поэтому тебе не нужно вручную останавливать или управлять жизненным циклом задач.