Coroutines

Coroutines are lightweight threads & creating coroutines doesn’t allocate new threads. They use predefined thread pools. It will enable writing asynchronous code in a synchronous way and makes the multitasking very easy. coroutines can run in parallel, wait for each other and communicate.

function fetchUser will look like below:

suspend fun fetchUser(): User {
    return GlobalScope.async(Dispatchers.IO) {
        // make network call
        // return user
    }.await()
}

fetchAndShowUser like below:

suspend fun fetchAndShowUser() {
    val user = fetchUser() // fetch on IO thread
    showUser(user) // back on UI thread
}

And the showUser function :

fun showUser(user: User) {
    // show user
}
  • Dispatchers: Dispatchers help coroutines in deciding the thread on which the work has to be done. There are majorly three types of Dispatchers which are as IO, Default, and Main
  • IO dispatcher is used to do the network or reading and writing from files. In short – any input and output. 
  • Default is used to do the CPU intensive work, such as sorting large lists, doing complex calculations. 
  • Main is the UI thread of Android. Dispatcher for performing UI-related events. For example, showing lists in a RecyclerView, updating Views and so on
In order to use these, we need to wrap the work under the async function.
        EX : suspend fun async()
  • suspend: Suspend function is a function that could be started, paused, and resume. Suspend functions are only allowed to be called from a coroutine or another suspend function. We can see the async function which includes the keyword suspend. So, in order to use that, we need to make our function suspend too.

    So, the fetchAndShowUser can only be called from another suspend function or a coroutine. We can't make the onCreate function of an activity suspend, so we need to call it from the coroutines like below:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    GlobalScope.launch(Dispatchers.Main) {
        fetchAndShowUser()
    }
    
}

Which is actually

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    GlobalScope.launch(Dispatchers.Main) {
        val user = fetchUser() // fetch on IO thread
        showUser(user) // back on UI thread
    }
    
}

showUser will run on UI thread because we have used the Dispatchers.Main to launch it.

There are two functions in Kotlin to start the coroutines which are as follows:

  • launch{}
  • async{}

Launch vs Async in Kotlin Coroutines

The difference is that the launch{} does not return anything and the async{}returns an instance of Deferred<T>, which has an await()function that returns the result of the coroutine like we have future in Java in which we do future.get() to the get the result.

In other words:

  • launch: fire and forget
  • async: perform a task and return a result

We have a function fetchUserAndSaveInDatabase like below:

suspend fun fetchUserAndSaveInDatabase() {
    // fetch user from network
    // save user in database
    // and do not return anything
}

Now, we can use the launch like below:

GlobalScope.launch(Dispatchers.Main) {
    fetchUserAndSaveInDatabase() // do on IO thread
}

As the fetchUserAndSaveInDatabase does not return anything, we can use the launch to complete that task and then do something on Main Thread.

But when we need the result back, we need to use the async.

We have two functions which return User like below:

suspend fun fetchFirstUser(): User {
    // make network call
    // return user
}

suspend fun fetchSecondUser(): User {
    // make network call
    // return user
}

Now, we can use the async like below:

GlobalScope.launch(Dispatchers.Main) {
    val userOne = async(Dispatchers.IO) { fetchFirstUser() }
    val userTwo = async(Dispatchers.IO) { fetchSecondUser() }
    showUsers(userOne.await(), userTwo.await()) // back on UI thread
}

Here, it makes both the network call in parallel, await for the results, and then calls the showUsers function.

There is something called withContext.

suspend fun fetchUser(): User {
    return GlobalScope.async(Dispatchers.IO) {
        // make network call
        // return user
    }.await()
}

withContext is nothing but another way of writing the async where we do not have to write await().

suspend fun fetchUser(): User {
    return withContext(Dispatchers.IO) {
        // make network call
        // return user
    }
}

But there are many more things that we should know about the withContext and the await.

Now, let's use withContext in our async example of fetchFirstUser and fetchSecondUser in parallel.

GlobalScope.launch(Dispatchers.Main) {
    val userOne = withContext(Dispatchers.IO) { fetchFirstUser() }
    val userTwo = withContext(Dispatchers.IO) { fetchSecondUser() }
    showUsers(userOne, userTwo) // back on UI thread
}

When we use withContext, it will run in series instead of parallel. That is a major difference.

The thumb-rules:

  • Use withContext when you do not need the parallel execution.
  • Use async only when you need the parallel execution.
  • Both withContext and async can be used to get the result which is not possible with the launch.
  • Use withContext to return the result of a single task.
  • Use async for results from multiple tasks that run in parallel.

Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API