Какой scope использовать для запуска корутин в ViewModel
Что проверяют
Знаете ли вы стандартный ответ и что за ним стоит: какой диспетчер у viewModelScope, когда он отменяется и что делать с работой, которая должна пережить экран. Следом часто спрашивают про GlobalScope, про тестирование такой ViewModel и про сбор Flow в UI.
Как отвечать
Ответ: viewModelScope, свойство-расширение из androidx.lifecycle. Что про него важно сказать:
- отмена: scope отменяется, когда ViewModel очищается (
onCleared), то есть при окончательном уходе с экрана, а не при повороте. Загрузка переживает смену конфигурации; - диспетчер: по умолчанию
Dispatchers.Main.immediate. Suspend-функции Retrofit и Room уже main-safe, а свой блокирующий код уходит вwithContext(Dispatchers.IO), лучше внутри репозитория; SupervisorJob: падение одной корутины не отменяет соседние, но необработанное исключение всё равно уронит приложение, поэтому нуженtry/catchвнутри корутины илиCoroutineExceptionHandler;- состояние:
StateFlowчерезstateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initial), чтобы поток не работал без подписчиков.
Чего не брать: GlobalScope, корутину в нём никто не отменит, и lifecycleScope Activity внутри ViewModel, он живёт меньше, потому что Activity пересоздаётся при повороте. Если работа должна завершиться даже после ухода пользователя, например отправка формы, берут WorkManager или scope уровня приложения, внедрённый через Hilt.
В тестах главный диспетчер подменяют: Dispatchers.setMain(StandardTestDispatcher()) из kotlinx-coroutines-test плюс runTest.
Где спотыкаются
Говорят, что viewModelScope сам уводит работу в фон. Он работает на главном потоке, и блокирующий вызов без withContext подвесит интерфейс.
Оборачивают launch в try/catch снаружи. Так исключение не поймать, обработка нужна внутри самой корутины.
Ловят всё через catch (e: Exception) и глотают CancellationException. Корутина перестаёт корректно отменяться, и работа продолжается после ухода с экрана.