Let's dissect the viewModelScope first from it's source code. It uses a custom implementation of CouroutineScope. The context comprises of a SupervisorJob and a Dispatchers.Main dispatcher. This ensures that the coroutine is launched on the main thread and it's failure doesn't affect other coroutines in the scope.
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
Couple of examples worth exploring.
viewModelScope.launch {
Log.d("ViewModel", "Just viewModelScope: ${Thread.currentThread().name}")
}
// Output: Just viewModelScope: main
viewModelScope.launch(Dispatchers.IO) {
Log.d("ViewModel", "IO viewModelScope: ${Thread.currentThread().name}")
}
// Output: IO viewModelScope: DefaultDispatcher-worker-3
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
// Output:
// viewModelScope thread: main
// withContext thread: DefaultDispatcher-worker-4
I checked and it is indeed sequential, but in the documentation i
found that it doesn't have to be.
withContext() is a suspending operation and the coroutine will suspend till it's completion and then proceed ahead. That is apparent from the third example above.
In summary, viewModelScope will use main thread to execute a coroutine whose cancellation won't affect other couroutines. Use withContext when you want to do heavy task off of main thread in a suspending fashion; dispatch it using an appropriate dispatcher. Kotlin Coroutine guide is worth a read.
Edit:
Consider the below code as a single unit of execution. This illustrates the fact that when using withContext(), the caller thread is suspending, but it is not blocked, which allows it to go ahead and pick up some other pending work. The interleaving of the output loggers is of interest to us.
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
viewModelScope.launch {
Log.d("ViewModel", "I'm not blocked: ${Thread.currentThread().name}")
}
// Output:
// viewModelScope thread: main
// I'm not blocked: main
// withContext thread: DefaultDispatcher-worker-2
// I'm finished!