Things you should know before getting started with Coroutine on Android.

In short, Coroutine is a way to write Asynchronous code in the straight-forward Synchronous way without a bunch of callbacks.

By now, every Android developer should have heard about Coroutine. Over the past few years, Coroutine has gained its popularity in the Android community along with Kotlin. In this post, I will talk about the foundations of Coroutine that I wish I knew when I first start exploring with Coroutine.

In short, Coroutine is a way to write Asynchronous code in the straight-forward Synchronous way without a bunch of callbacks.

Let's not waste our time. Here is an example of how we can use Coroutine.

class ArtistViewModel(
    private val getArtistsUseCase: GetArtistsUseCase
) : ViewModel(), CoroutineScope {

    private val job = Job()

    val artistsLiveData: MutableLiveData<List<Artist>> = MutableLiveData()

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    fun getArtist() {
        launch {
            val artistResponse = withContext(Dispatchers.IO) {
                getArtistsUseCase("Slayer")
            }

            artistsLiveData.value = artistResponse?.result?.map {
                Artist(it.name, it.genreName)
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

In the code, there is a ViewModel class. We use Coroutine to fetch the list of Artist objects from API via getArtistUseCase. This approach might be considered an old-school way of using Coroutine with ViewModel already. There is an easier approach but let's save that for later.

Looking for a new challenge? Join Our Team



Before going any further, I will explain the following important keywords.

  • Coroutine context defines how our Coroutines will be executed such as running on UI Thread or operating IO Thread. Coroutine Context consists of various elements. The two main elements are the Job of the Coroutine and its Dispatcher

  • Job is conceptually a cancellable thing, and it can be arranged into parent-child hierarchies. The cancellation of a parent leads to the cancellation of all its children.

  • Dispatchers determine thread  ( or threads) on which the corresponding coroutine will be executed. There are two most commonly used dispatchers. Dispatchers.Main is used when we want our Coroutine to run on the UI Thread. Whereas, Dispatchers.IO is for Coroutine that blocks the current thread such as calling API service, or accessing databases or files.

  • Coroutine Builder is for building up new Coroutine. There are three main builders.
    • launch; launch might be the most used Builder as it is the easiest way to create a Coroutine. The Coroutines crated by `launch` will not block the current running thread.
    • async; async is a builder that can return value out of Coroutine.
    • runBlocking; This builder is the opposite of launch because it will block the running Thread. You will mostly use this builder for writing tests for your Coroutines.

  • CoroutineScope defines a scope for new coroutines.  Coroutine builder (like launch, async, etc) is an extension on CoroutineScope and inherits its CoroutineContext to automatically propagate all its elements and cancellation. 

Let's get back to our example. We'll build Coroutine to fetch data from API service. Let's break it down.

To build Coroutine, we need to define the Coroutine Context for our Coroutine Builder

class ArtistViewModel( 
    private val getArtistsUseCase: GetArtistsUseCase
): ViewModel(), CoroutineScope {
   private val job = Job() 
    
   override val coroutineContext: CoroutineContext        
        get() = job + Dispatchers.Main
    ...
    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

We make class ArtistViewModel implements CoroutineScope that also forces us to override the Coroutine Context.
override val coroutineContext: CoroutineContext        
        get() = job + Dispatchers.Main

The above section means that the Default Dispatcher of Coroutine in this Class is Dispatchers.Main, and job allow us to control the cancellation of Coroutine in this class by calling job.cancel()

In the example, we call job.cancel() in onCleared() so that our Coroutines will stop in case the viewModel is destroyed.

Let's move to the part where the Coroutine is built.

launch {    
    val response = withContext(Dispatchers.IO) {       
        getArtistsUseCase("Slayer")            
    } 
            
    artistsLiveData.value = response?.result
}

Since we need to call the API service. This how we switch from executing on the Main thread to the IO thread. We use withContext to switch from Dispatcher.Main to Dispatcher.IO. The code inside withContext block is going to be executed on the IO thread.

The concept above can be applied to the other things apart from ViewModel such as Presenter, Activity, or Fragment.

Actually, with Android ViewModel in  AndroidX lifecycle v2.1.0 libs, there is viewModelScope representing the Coroutine scope of ViewModel that tides to the lifecycle of ViewModel. Therefore, we don't need to call job.cancel() in onClear() anymore. See the example down below.

class ArtistViewModel(
    private val getArtistsUseCase: GetArtistsUseCase
) : ViewModel() {
    val artistsLiveData: MutableLiveData<List<Artist>> = MutableLiveData()

    fun getArtist() {
        viewModelScope.launch {
            val artistResponse: ResponseWrapper<ArtistApi>? = withContext(Dispatchers.IO) {
                getArtistsUseCase("Slayer")
            }

            artistsLiveData.value = artistResponse?.result?.map {
                Artist(it.name, it.genreName)
            }
        }
    }
}

RIP Jerry Stiller :(

You can also check out the code on my repository.


Source

Blog's cover image is from Tool, Fear Inoculum album's artwork.
Like 7 likes
Ben Kitpitak
Mobile Developer at OOZOU in Bangkok, Thailand
Share:

Join the conversation

This will be shown public
All comments are moderated

Get our stories delivered

From us to your inbox weekly.