Как запустить корутину во ViewModel
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Запуск корутин во 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