Kotlin Coroutine Basic-1
Before start work on the coroutine, First, we have to know about the most common component using in a coroutine. Let’s start with some components which are basically used in a coroutine.
Coroutine Scope:
The scope simply provides lifecycle methods for coroutines and allows us to manage coroutines to start and stop them. There are libraries that actually help us do structured concurrency without having to write boilerplate in every ViewModel
. In order to use these libraries, we need to know the types of coroutine scopes that exist.
1. ViewModelScope
Coroutines in this scope are useful when there is work that should only be done when a ViewModel is active. To avoid boilerplate, add the code below to your build.gradle
file.
implementation “androidx.lifecycle:lifecycle-viewmodel-ktx:$view_model_scope_version”
This library adds viewModelScope
as an extension function and binds the scope to Dispatchers.Main
by default. However, the dispatcher can be changed if need be. The job is also automatically canceled when the ViewModel is cleared, so all we have to do is this:
class MainViewModel : ViewModel()
{
private fun doSomething()
{
viewModelScope.launch(Dispatchers.Default)
{ //Specify dispatcher if you like
// Coroutine is launched. Time to do something.
}
}
// No need to override onCleared, it's taken care of :)
}
2. LifeCycleScope
A LifecycleScope
is defined for each Lifecycle object. LifecycleOwner
could be an Activity or a Fragment. Any coroutine lanced in this scope is canceled when the Lifecycle is destroyed. You can access the Lifecycle’s CoroutineScope via lifecycle.coroutineScope
or lifecycleOwner.lifecycleScope
properties. To use this, add the following dependency to your build.gradle
file.
implementation “androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_scope_version”
Launching a coroutine using this scope can be done like this:
class HomeFragment: Fragment() {
...
private fun doSomething()
{
lifecycleOwner.lifecycleScope.launch {
// Coroutine launched. Do some computation here.
}
}
}
3. Global Scope
Global Scope is one of the ways by which coroutines are launched. When Coroutines are launched within the global scope, they live long as the application does. If the coroutines finish it’s a job, it will be destroyed and will not keep alive until the application dies, but let’s imagine a situation when the coroutines has some work or instruction left to do, and suddenly we end the application, then the coroutines will also die, as the maximum lifetime of the coroutine is equal to the lifetime of the application. Coroutines launched in the global scope will be launched in a separate thread. Below is the example which shows that the in-global scope coroutines are launched in a separate thread.
class HomeFragment: AppCompatActivity() {
...
private fun doSomething()
{
GlobelScope.lifecycleScope.launch {
// Coroutine launched. Do some computation here.
}
}
}
Coroutine Builder
Coroutine builders are functions that help us create a coroutine. They can be called from normal functions because they do not suspend themselves. Three coroutine builders are listed below.
1. runBlocking
2. launch
3. async
runBlocking
As the name suggests, runBlocking is a coroutine builder that blocks the current thread until all tasks of the coroutine it creates, finish. So, why do we need runBlocking when we are clearly focusing to avoid blocking the main thread?
We typically runBlocking to run tests on suspending functions. While running tests, we want to make sure not to finish the test while we are doing heavy work in test suspend functions.
launch
launch is essentially a Kotlin coroutine builder that is “fire and forget”. This means that launch creates a new coroutine that won’t return any result to the caller. It also allows to start a coroutine in the background.You can create a new coroutine from launch as follows:
You can create a new coroutine from launch as follows:
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlockingfun main() {
println("Starting main function..") GlobalScope.launch {
println(doSomething())
}
runBlocking {
delay(4000L) // make sure to keep the JVM alive in order to wait for doSomething() to execute
}
}suspend fun doSomething() : String {
delay(3000L) // simulate long running task
return "Did something that was 3 seconds long"
}
In the above code, we call the suspending function, doSomething() and after 3 seconds of delay we print the string: “Did something that is 3 seconds long”.
Note: “delay” in Kotlin, is a suspend function which delays a coroutine without blocking the current thread. It then resumes the coroutine after the specified time (In our case, 3 seconds or 3000 milliseconds).
async
We now move on to the best part of Kotlin Coroutines builders — async.
async is a coroutine builder which returns some value to the caller. async can be used to perform an asynchronous task which returns a value. This value in Kotlin terms is a Deferred<T> value.
We can simply use async to create a coroutine doing some heavy work like so:
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlockingfun main() {
println("Hello, ")
GlobalScope.async {
doSomething()
}
runBlocking {
delay(3000L) // only used to keep the JVM alive
}
}
suspend fun doSomething() {
delay(2000L)
print("this is bad example for using async!!!")
}
The output for the above code would be, “Hello, this is a bad example for using async!!!”.
However, if doSomething() function doesn’t return anything, what is its use?
I have created the above example precisely to point out this error. It is highly discouraged to use async when you have nothing to return. Using launch should suffice in this case, like so:
import kotlinx.coroutines.*fun main() {
println("Hello, ")
GlobalScope.launch {
doSomething()
} runBlocking {
delay(3000L) // only used to keep the JVM alive
}
}
suspend fun doSomething() {
delay(2000L)
print("this is an example for using launch when async doesn't return anything!!!")
}
Coroutine Context
A context is a set of data that related to the coroutine. In simple words, Context is a set of variables that you can access inside of the coroutine. All coroutine has an associate context. Also, CoroutineContext#get is remarkable as it provides type-safety in the lookup of heterogeneous elements:
All of the coroutine classes implement CoroutineScope and have the property coroutineContext. Therefore, we can access coroutineContext in the coroutine block:
runBlocking {
Assertions.assertNotNull(coroutineContext)
}
And we can read an element of a coroutineContext:
runBlocking {
Assertions.assertNotNull(coroutineContext[Job])
}
Scope and Context are very similar only difference is that scope used to create and manage coroutine. Whereas Context is the set of variables and data associate with that coroutine.
An important element of Coroutines Context
- Dispatcher — It simply decides which thread the coroutine runs on. We’ll learn more about Dispatcher in the upcoming blog.
- Job — It basically handles the coroutine lifecycle. This concept also I’ll write separate articles on that.
Let’s discuss more about that topic in part-2 soon.
Thanks for reading!