'Why does viewModelScope.launch run on the main thread by default

While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.

When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.

This confuses me because the docs of viewModelScope.launch { } clearly state:

Launches a new coroutine without blocking the current thread

Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?

I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.

What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.

viewModelScope.launch(Dispatchers.IO){ liveData.postValue(someRepository.someWork()) }

So my second question is, is this the way to go ?



Solution 1:[1]

ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.

For you example:

fun thisWillRunOnMainThread() {

    viewModelScope.launch { 

        //below code will run on UI thread.
        showLoadingOnUI()

        //using withContext() you can run a block of code on different dispatcher
        val result = novel.id = withContext(Dispatchers.IO) {
            withsomeRepository.someWork()
        }

        //The below code waits until the above block is executed and the result is set.
        liveData.value = result
        finishLoadingOnUI()
    }
}

For more reference, I would say there are some neat articles that will help you understand this concept.

Medium link that explains it really neat.

Solution 2:[2]

So my second question is, is this the way to go ?

I would expect two things to be different in your current approach.

1.) First step would be to define the scheduler of the background operation via withContext.

class SomeRepository {
    suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
        ...
    }
}

This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".

2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.

val liveData: LiveData<SomeResult> = liveData {
    emit(someRepository.someWork())
}

Which in a ViewModel, you would use like so:

val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
    withContext(Dispatchers.IO) {
        emit(someRepository.someWork())
    }
}

And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.

Solution 3:[3]

The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen

Suppose if by default if the launch runs on IO thread then the code would look like this

viewmodelScope.launch {
  val response = networkRequest() 
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Suppose if by default if the launch runs on Default thread then the code would look like this

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Since the default launch is on main thread, now you have to do below

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  renderUI(response)
}

To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ngoa
Solution 2
Solution 3 murali kurapati