Difference Between CoroutineScope and coroutineScope in Kotlin
- Create a New Kotlin Project
- Create a Button on the Main Layout
-
Use
CoroutineScope
in Kotlin -
Use
coroutineScope
in Kotlin - Conclusion
The Kotlin documentation defines coroutine as an execution that can be suspended as it is waiting for some background tasks to be executed, like downloading a resource from a network.
Coroutine helps us achieve concurrency since other computations continue executing when a coroutine is suspended. Note that the coroutines are independent of the thread they are using because it is not guaranteed that they will resume execution on the thread they were using.
To create a new coroutine, we must do so inside a scope. In this tutorial, we will learn how the scope in unstructured concurrency affects child coroutines and how to solve the scope problem using structured concurrency.
Create a New Kotlin Project
This tutorial will utilize IntelliJ IDEA, but you can use any preferred development environment.
Open IntelliJ IDEA and select File > New > Project
. On the window that opens, select Android
on the bottom left side, then select Empty activity
on the right side, as shown below.
Press the button labeled Next
and on the window that opens, enter the project name as CoroutineScope
, enter the package name as com.coffeedev.coroutinescope
, select Kotlin
on the Language section, and select API 19
on the Minimum SDK section.
Ensure these details are as shown below.
Press the button labeled Create
to generate a new Android project. This action creates a new application containing an activity named MainActivity
and a layout called activity_main
.
We will use these files to test the examples we cover in this tutorial. Ensure you have an active internet connection to add the required dependencies to our application.
To work with coroutines, we need to add the kotlinx-coroutines-core
dependency to our project. Copy and paste the following dependency in the build.gradle
file to add the coroutine dependency.
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
Create a Button on the Main Layout
Open the acivity_main.xml
layout file under src/main/res/layout
and copy and paste the following code into the file.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="@string/button_text"/>
</LinearLayout>
This code creates a LinearLayout
that contains only one View
of type Button
. We will use this button to invoke the coroutines in our application.
Ensure the final layout is as shown below.
The button is labeled using a string resource as indicated by the text
attribute. Copy and paste the following string resource in the file named strings.xml
located under the src/main/res/values
folder.
This creates a text for our button, and this text is accessed using the name button_text
.
<resources>
<string name="app_name">CoroutineScope</string>
<string name="button_text">Press Me</string>
</resources>
Use CoroutineScope
in Kotlin
In the introduction section, we mentioned that to create a new coroutine, we must do so inside a scope. This is where the CoroutineScope
comes in place.
To view this in action, copy and paste the code below into the MainActivity.kt
file under the src/main/java/com/coffeedev/coroutinescope
folder.
package com.coffeedev.coroutinescope
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
val message = getLoopProduct();
Toast.makeText(applicationContext, "Message: $message", Toast.LENGTH_LONG)
.show();
}
}
}
private suspend fun getLoopProduct(): Int {
var value = 1;
CoroutineScope(Dispatchers.IO).launch {
for (number in 1..5) {
delay(15);
value *= number;
}
}
return value;
}
}
In this code, we have created a suspend
function named getLoopProduct()
that returns the product of a for
loop. The for
loop is executed using a coroutine that runs using the Dispatchers.IO
threads, passed as the argument of the CoroutineScope()
.
For each iteration of the for
loop, there is a delay of 15 milliseconds, suspending the currently executing thread.
In the onCreate()
life cycle method, we have created a new scope that runs using the Dispatchers.Main
thread which is simply the main thread. Note that the coroutine of the getLoopProduct()
is a child of the coroutine created inside the onCreate()
method because the suspend
function is invoked inside it.
Coroutines created from different scopes run independently. Since the child coroutine uses a different scope from the parent coroutine, the parent does not wait for the child to finish executing.
This type of execution is referred to as Unstructured Concurrency.
The coroutine in our onCreate()
method executes only once and terminates. This means that the child coroutine continues running in the background and can expose our application to memory leaks.
We use the button created in our layout to display a Toast
containing the value returned by the getLoopProduct()
. The setOnClickListener()
method displays the Toast
on the screen when we press the button.
Run this code and note that the Toast
displays a value of 1
since the parent coroutine was suspended before the child coroutine finished executing.
Output:
Use coroutineScope
in Kotlin
The difference between the CoroutineScope()
and coroutineScope()
is that the latter creates a new scope without creating a new coroutine. The child coroutine uses the parent coroutinescope, which ensures that it completes before the parent coroutine completes execution.
This type of execution is referred to as Structured Concurrency.
To view this in action, replace the suspend
function in the previous example with the one provided below.
private suspend fun getLoopProduct(): Int {
var value = 1;
coroutineScope {
for (number in 1..5) {
delay(15);
value *= number;
}
}
return value;
}
Since the code in the onCreate()
method does not change, the child coroutine uses the parent scope, which runs in the main thread to execute the for
loop. The parent coroutine waits for the child coroutine to execute the for
loop before it terminates.
Run this code and note that the Toast
displays a value of 1 20
. Two indicates that the child coroutine is executing the entire loop without termination due to reusing the parent scope.
Output:
Conclusion
In this tutorial, we have learned how the scope of unstructured concurrency affects child coroutines and how to solve the problem using structured concurrency. The main topics we have covered are how to use CoroutineScope()
to create independent scopes and how to use coroutineScope()
to reuse the parent scope.
David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.
LinkedIn GitHub