CoroutineContext in Kotlin

Authors
  • Amit Shekhar
    Name
    Amit Shekhar
    Published on
CoroutineContext in Kotlin

I am Amit Shekhar, Co-Founder @ Outcome School, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

Join Outcome School and get high paying tech job: Outcome School

Before we start, I would like to mention that, I have released a video playlist to help you crack the Android Interview: Check out Android Interview Questions and Answers.

In this blog, we will learn about the CoroutineContext in Kotlin by going through the source code. We will also cover how to customize it.

We will start with the basics to understand everything about CoroutineContext.

Let's begin.

What is CoroutineContext in Kotlin?

CoroutineContext is an interface in Kotlin's coroutines that helps us define the context or the environment in which a coroutine executes, using various elements.

It helps us define the following elements:

  • Dispatcher: It helps Coroutines in deciding the thread on which the task has to be done.
  • Job: It represents the lifecycle of a coroutine, including its cancellation and completion states.
  • CoroutineName: It helps in providing a name for the coroutine, hence useful for debugging.
  • CoroutineExceptionHandler: It is used to handle uncaught exceptions in coroutines.

If we see the source code of the launch in Coroutines:

// code changed to make it simple
fun <SomeScope>.launch(context: CoroutineContext = ...) {
    // code removed for brevity
}

The first parameter is CoroutineContext.

Now, let's see the source code of the CoroutineContext.

public interface CoroutineContext {

    public operator fun <E : Element> get(key: Key<E>): E?

    // code removed for brevity

    public operator fun plus(context: CoroutineContext): CoroutineContext

    // code removed for brevity
}

The source code of the Element.

public interface CoroutineContext {

      // code removed for brevity

  public interface Element : CoroutineContext {

      // code removed for brevity

  }

}

By seeing the source code, we can see that CoroutineContext has a set of elements (CoroutineContext.Element) and these elements define the behavior of a coroutine.

At the beginning, we learned about those four elements:

  • Dispatcher
  • Job
  • CoroutineName
  • CoroutineExceptionHandler

These elements must be the type of CoroutineContext.Element internally. Let me show you the source code to understand.

Job Source Code:

public interface Job : CoroutineContext.Element {
  // code removed for brevity
}

Job is of type CoroutineContext.Element.

Similarly, if we see the source code for Dispatcher, CoroutineName, and CoroutineExceptionHandler, we can see that they are also the type of CoroutineContext.Element internally.

That is why, we can create a CoroutineContext using the plus (+) operator to define all the elements as below:

val coroutineContext: CoroutineContext =
    Dispatchers.IO +
    Job() +
    CoroutineName("OutcomeSchoolCoroutine") +
    CoroutineExceptionHandler { _, _ -> /* Handle Exception */ }

And then use it like below:

GlobalScope.launch(coroutineContext) {
  // do some work
}

Note: I have used GlobalScope for quick examples, we should avoid using it at all costs. In an Android project, we should use custom scopes based on our usecase such as lifecycleScope, viewModelScope etc.

Now we need to learn how to define and use each of the above-mentioned elements. To do this, we'll explore the customizations that can be easily made within the CoroutineContext.

Customization in CoroutineContext

Let's see the Hello World of the Coroutine:

GlobalScope.launch {
  // do some work
}

In the example mentioned above, the default CoroutineContext will be used since we have not provided a custom CoroutineContext.

As we know, CoroutineContext helps manage the Dispatcher, Job, CoroutineName, and CoroutineExceptionHandler. We can modify one or more of these elements based on our use case.

Initially, we will start by changing only the Dispatcher to gain a basic understanding.

We can write the code as below:

GlobalScope.launch(Dispatchers.IO) {
  // do some work
}

Here, we have specified the Dispatcher for the Coroutine to use during task execution. The task will be executed on the IO Dispatcher.

Now, let's suppose we want to change the CoroutineName in addition to the Dispatcher.

We can write the code as below:

GlobalScope.launch(Dispatchers.IO + CoroutineName("OutcomeSchoolCoroutine")) {
  // do some work
}

Here, we have used the plus operator.

Now, suppose we want to change other parameters in addition to the Dispatcher and CoroutineName. We can use the + operator again to add the other parameters as shown below:

GlobalScope.launch(
    Dispatchers.IO +
            Job() +
            CoroutineName("OutcomeSchoolCoroutine") +
            CoroutineExceptionHandler { _, _ -> /* Handle Exception */ }
) {
  // do some work
}

Or, we can create a CoroutineContext:

val coroutineContext: CoroutineContext =
    Dispatchers.IO +
    Job() +
    CoroutineName("OutcomeSchoolCoroutine") +
    CoroutineExceptionHandler { _, _ -> /* Handle Exception */ }

And then use it like below:

GlobalScope.launch(coroutineContext) {
  // do some work
}

This is how we can easily customize the CoroutineContext.

Now, we have understood the CoroutineContext in Kotlin.

Prepare yourself for Android Interview: Android Interview Questions

That's it for now.

Thanks

Amit Shekhar
Co-Founder @ Outcome School

You can connect with me on:

Follow Outcome School on:

Read all of our high-quality blogs here.