14

Currently my code looks like this i have a ViewModel that calls the repository to do some background computations and return a result.
ViewModel function is run with viewModelScope.launch(Dispatchers.IO) and then the repository one is a suspend function.
Do I have to use return withContext{} to ensure that everything will be done sequentially? I checked and it is indeed sequential, but in the documentation i found that it doesn't have to be?

baltekg
  • 985
  • 9
  • 31

1 Answers1

20

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!
Siddharth Kamaria
  • 2,448
  • 2
  • 17
  • 37
  • The point is that it is sequentional without using withContext, just with viewmodelscope.launch executing suspending function – baltekg Sep 25 '20 at 08:51
  • Are you alluding to the fact that it is sequential with or without the use of `withContext`? – Siddharth Kamaria Sep 25 '20 at 08:57
  • If that's the case; the power of suspending function is that the execution is offloaded on to a different thread when you use `withContext` and while the other thread is working, the calling thread can go and finish some other work. – Siddharth Kamaria Sep 25 '20 at 08:59
  • yeah, that what i wanted to know. But i still cannot find an example where it won't be not in the right order when using `viewModelScope` without the `withContext`. As an example I added a function call after the one suspending that only prints some text and in theory because the suspended function should be suspended, the second one should be printed before I get the result from the background one. And I still works in order - first i get the result from suspending call and print comes only after it. – baltekg Sep 25 '20 at 09:51
  • 1
    Ok, thanks very much. Now i understood that I was thinking about it in a wrong way, that you need withContext when you want a result from the coroutine, not inside a coroutine which is the case in my situation. – baltekg Sep 25 '20 at 11:11
  • @baltekg The execution will be sequential (with suspensions) for the code within a coroutine given that there are no nested threads / coroutines. I didn't quite follow your example, if you can add some supporting code I can help you better. – Siddharth Kamaria Sep 25 '20 at 11:58
  • 1
    Couple of clarifications - `withContext` makes the code within it suspending while the execution shifts to another thread, it is NOT a coroutine builder. Coroutine builders are `launch` and `async`. If you want to get a result back asynchronously from a coroutine, you can explore `async / await` instead of `launch`. – Siddharth Kamaria Sep 25 '20 at 12:01