Android notes: Understanding viewModelScope.launch{}

Tristan Elliott - May 5 '23 - - Dev Community

Table of contents

  1. What we are talking about
  2. Main parts to a coroutine
  3. Background work
  4. Best practice

My app on the Google Playstore

GitHub code

Resources

What I am trying to understand

  • So inside my Android app I use a lot of code like this:
viewModelScope.launch{
  // do some work here
}

Enter fullscreen mode Exit fullscreen mode
  • And up until now, I used it without any sort of real understanding of what is going on. So by the end of this blog post both you and I should have a better understanding of how this code works.

The 4 main parts to a coroutine.

  • Within a coroutine there are 4 main parts

1) Scope
2) Builder
3) Dispatcher
4) Context

1) Scope

  • All coroutine work is managed by a coroutine scope. Primarily a coroutine scope is responsible for canceling and cleaning up coroutines when the coroutine scope is no longer needed. With the previously mentioned code, the scope is viewModelScope. A viewModelScope is defined for each ViewModel in our app. Any coroutine launched in this scope is automatically canceled if the ViewModel is cleared. This is useful for any work that needs to be done only when the ViewModel is active. By destroying the scope when the ViewModel is destroyed, it saves us from potentially wasting resources and memory leaks. Thanks to viewModelScope all of our coroutine clean up and setup is done for us.

2) Builder

  • All coroutines start with a coroutine builder. The block of code passed to the builder, along with anything called from that code block(directly or indirectly), represents the coroutine. For us the builder is launch{}, launch{} is a fire and forget coroutine builder. Which allows us to pass it a lambda expression to form the route of the coroutine. Since we don't have to wait for any sort of value we call it and forget about it

3) Dispatcher

  • When using a coroutine builder, we can provide it a dispatcher. The dispatcher is what indicated what thread pool should be used for the executing the code inside the coroutine.
  • We know with viewModelScope.launch{} there is a scope and a builder, but where is the dispatcher? As it turns out, when we use launch{} without any parameters, it inherits the dispatcher from the coroutine scope. So that means that we inherit the dispatcher from viewModelScope. According to documentation the dispatcher of viewModelScope is hardcoded to Dispatchers.Main. Meaning, unless we provide a dispatcher to launch{}, all the work will be done on the main UI thread. Which is not good and we will see later on how we can avoid this.

4) Context

  • This is the area of coroutines I am the most unfamiliar with, so I apologize for the brevity
  • The dispatcher that we provide to a coroutine builder is always part of a CoroutineContext. As the name suggests the CoroutineContext provides a context for executing a coroutine.

Background work

  • If you are like me and you are using viewModelScope.launch{}, there is a good chance you are using wrong. Before I started reading about coroutines my code looked like this:
fun getCalves() = viewModelScope.launch(){


        getCalvesUseCase.execute(_uiState.value.calfLimit)
            .collect{response ->
            _uiState.value = _uiState.value.copy(
                data = response,
            )
        }

    }

Enter fullscreen mode Exit fullscreen mode
  • Which seems perfectly fine until you realize getCalvesUseCase.execute is making a network request and all that work is being done on the main UI thread and causing some Jank. Jank is defined as:

Android renders UI by generating a frame from your app and displaying it on the screen. If your app suffers from slow UI rendering, then the system is forced to skip frames. When this happens, the user perceives a recurring flicker on their screen, which is referred to as jank.

  • How can we fix some Jank?

  • You may have noticed from the code block above that I am using .collect{} which means I am using Flows. So now we have to figure out how to change dispatchers with the Flow api. This is pretty easy thanks to flowOn. To change the dispatcher of a flow we can use flowOn. This will make the producer of the flow(code that calls emit) work on the background thread and the upstream of the flow will be unaffected. The new and approved code looks like this:

fun getCalves() = viewModelScope.launch(){


   getCalvesUseCase.execute(_uiState.value.calfLimit)
            .flowOn(Dispatchers.IO) //network requests done on 
             //background thread 
            .collect{response -> // done on main thread
            _uiState.value = _uiState.value.copy(
                data = response,
            )
        }
    }

Enter fullscreen mode Exit fullscreen mode
  • Thanks to flowOn all the network requests are done on the Dispatchers.IO which is specifically optimized to handle network requests.

Best Practice

  • It is considered best practice to inject dispatchers into your ViewModel:
class MainViewModel  constructor(
  private val dispatcherIO: CoroutineDispatcher = Dispatchers.IO

):ViewModel() {
   fun getCalves() = viewModelScope.launch(){


   getCalvesUseCase.execute(_uiState.value.calfLimit)
            .flowOn(dispatcherIO) //network requests done on 
             //background thread 
            .collect{response -> // done on main thread
            _uiState.value = _uiState.value.copy(
                data = response,
            )
        }
    }

}

Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player