Coroutine in Kotlin

Coroutine in Kotlin

 

Dependency

// new version // kotlin coroutine library implementation(libs.kotlinx.coroutines.android) // old version implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")
  • core library already comes built in in android project

Basic Usage of Coroutines:

1. Launching a Coroutine:

To start a coroutine, you can use the launchfunction, which creates a new coroutine that runs on the main thread by default:
import kotlinx.coroutines.* fun main(){ GlobalScope.launch{ // Coroutine Code Here } }
 
2. Asynchronous Work:
Coroutines can be used to perform time-consuming operations without blocking the main thread. For instance, making a network request:
import kotlinx.coroutines.* fun fetchDataFromServer() { GlobalScope.launch(Dispatchers.IO) { // suspend function call val result = makeNetworkRequest() withContext(Dispatchers.Main){ // Update UI with Result } } } suspend fun makeNetworkRequest():String{ // simulate network request delay delay(3000) return "Data from server" }
In the above example, the makeNetworkRequest function is a suspend function. It can only be called from within a coroutine or another suspend function. The Dispatchers.IO specifies that the coroutine should run on the IO thread, suitable for network operations. After fetching the data, we switch back to the main thread using Dispatchers.Main to update the UI safely.

3. Error Handling:

Coroutines have built-in support for handling exceptions:
import kotlinx.coroutines.* fun doTask(){ GlobalScope.launch { try { performTask() } catch (e: Exception){ // Handle exception } } } suspend fun performTask(){ // Perform the task that might throw an exception }

4. Waiting for Multiple Coroutines:

You can use asyncto perform multiple tasks concurrently and await to wait for their results:
Thanks for reading…
import kotlinx.coroutines.* suspend fun fetchUserData(): UserData { return coroutineScope { val userDeferred = async { fetchUser() } val orderDeferred = async { fetchOrders() } val user = userDeferred.await() val order = orderDeferred.await() UserData(user, order) } } suspend fun fetchUser(): User { // simulate user data fetch delay delay(2000) return User("John Doe") } suspend fun fetchOrders(): List<Order> { // simulate orders fetch delay delay(1500) return listOf(Order("Order 1"), Order("Order 2")) } data class UserData(val user: User, val orders: List<Order>) data class User(val name: String) data class Order(val orderId: String)
 

Example

package com.example.coroutineexample import android.os.Bundle import android.util.Log import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.yield class MainActivity : AppCompatActivity() { private val TAG: String = "=====" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) CoroutineScope(Dispatchers.Main).launch{ task1() } CoroutineScope(Dispatchers.Main).launch { task2() } } // co-operating functions suspend fun task1() { Log.e(TAG, "STARTING TASK 1") yield() Log.e(TAG, "ENDING TASK 1") } suspend fun task2() { Log.e(TAG, "STARTING TASK 2") yield() Log.e(TAG, "ENDING TASK 2") } }

OutPut:

23:35:40.313 E STARTING TASK 1 23:35:40.321 E STARTING TASK 2 23:35:40.330 E ENDING TASK 1 23:35:40.333 E ENDING TASK 2
 
 

Coroutine

 
  • A coroutine is an instance of a suspendable computation.
  • It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code.
  • However, a coroutine is not bound to any particular thread.
  • It may suspend its execution in one thread and resume in another one.
 
import kotlinx.coroutines.*

First Coroutine Example

Concurrency

  • Kotlin's coroutines are much less resource-intensive than threads.
  • Each time you want to start a new computation asynchronously, you can create a new coroutine instead.
  • To start a new coroutine, use one of the main coroutine builders: launch, async, or runBlocking.
  • Different libraries can define additional coroutine builders.
  • async starts a new coroutine and returns a Deferred object.

Deferred

  • Deferred represents a concept known by other names such as Future or Promise.
  • It stores a computation, but it defers the moment you get the final result; it promises the result sometime in the future.
  • The main difference between async and launch is that launch is used to start a computation that isn't expected to return a specific result.
  • launch returns a Job that represents the coroutine.
  • It is possible to wait until it completes by calling Job.join().
  • Deferred is a generic type that extends Job.
  • An async call can return a Deferred<Int> or a Deferred<CustomType>, depending on what the lambda returns (the last expression inside the lambda is the result).
  • To get the result of a coroutine, you can call await() on the Deferred instance.
  • While waiting for the result, the coroutine that this await() is called from is suspended:
import kotlinx.coroutines.* runBlocking { val deferred: Deferred<Int> = async { loadData() } println("waiting...") println(deferred.await()) } suspend fun loadData(): Int { println("loading...") delay(1000L) println("loaded!") return 42 }
  • runBlocking is used as a bridge between regular and suspending functions, or between the blocking and non-blocking worlds.
  • It works as an adaptor for starting the top-level main coroutine.
  • It is intended primarily to be used in main() functions and tests. #%% md

Cancelling coroutine execution

  • In a long-running application you might need fine-grained control on your background coroutines.
  • For example, a user might have closed the page that launched a coroutine and now its result is no longer needed and its operation can be cancelled.
  • The launch function returns a Job that can be used to cancel the running coroutine:

Types of CoroutineScope

There are several types of CoroutineScope that you can use to create coroutines in Android. Some of the most common ones are:
  • GlobalScope: It is a CoroutineScope instance that runs on a global thread and is not tied to any Android component, such as an Activity or a Fragment. It can be used to perform tasks that are not linked to the user interface (UI) or to start a coroutine that is independent of the lifetime of any Android component.
  • viewModelScope: It is a ViewModel extension property that can be used to create coroutines that are cancelled when the ViewModel is destroyed. It is useful for performing asynchronous tasks that are related to the view, but that do not depend on the lifetime of a particular Activity or Fragment.
  • lifecycleScope: It is a LifecycleOwner (such as an Activity or Fragment) extension property that can be used to create coroutines that are cancelled when the LifecycleOwner enters a destruction state. It is useful for performing asynchronous tasks that depend on the lifetime of a particular Android component.

What is the role of Dispatchers?

The Dispatchers object is a class provided by Kotlin that gives access to different execution threads and is often used with coroutines to specify which thread a task should be run on.
For example:
  • Dispatchers.Main is used to run a task on the UI thread and is useful for updating the UI from a coroutine.
  • Dispatchers.IO is used to run I/O tasks such as network requests and Dispatchers.Default is used to run tasks on a default worker thread. Example of how to use the Dispatchers object to specify which thread a task should run on in a coroutine in an Android application:
+-----------------------------------+ | Dispatchers.Main | +-----------------------------------+ | Main thread on Android, interact | | with the UI and perform light | | work | +-----------------------------------+ | - Calling suspend functions | | - Call UI functions | | - Updating LiveData | +-----------------------------------+ +-----------------------------------+ | Dispatchers.IO | +-----------------------------------+ | Optimized for disk and network IO | | off the main thread | +-----------------------------------+ | - Database* | | - Reading/writing files | | - Networking** | +-----------------------------------+ +-----------------------------------+ | Dispatchers.Default | +-----------------------------------+ | Optimized for CPU intensive work | | off the main thread | +-----------------------------------+ | - Sorting a list | | - Parsing JSON | | - DiffUtils | +-----------------------------------+
 

There two ways to create / Build coroutine

1. launch { ... }

  • it will return job object that refers to currant coroutine thread
  • it has many functions available to manipulate coroutine execution

Example - 1

fun asyncExample() { CoroutineScope(Dispatchers.IO).launch { printFollowers() } } private suspend fun printFollowers() { var followers = 0 val job = CoroutineScope(Dispatchers.IO).launch { followers = getFbFollowers() } job.join() Log.d(TAG, "printFollowers: $followers") } private suspend fun getFbFollowers(): Int { delay(1000L) return 59 }

2. async { ... } / ( Async - Await )

  • when we want to perform task and want result data in return
  • it will return deferred object that refer that this thread might return some data in Future
  • deferred object’s most used method await()

Example - 1

fun asyncExample() { CoroutineScope(Dispatchers.IO).launch { printFollowers() } } private suspend fun printFollowers2() { val deferred = CoroutineScope(Dispatchers.IO).async { getFbFollowers() } Log.d(TAG, "printFollowers: ${deferred.await()}") } private suspend fun getFbFollowers(): Int { delay(1000L) return 59 }
 
Use Launch - when you do not care about the result. (Fire & Forget) Use Async - when you expect result/output from your coroutine
⚠️
Although both can be used to achieve the same functionality but it is better to use things that are meant for it.

yield()

  • this is suspend function.
  • it will suspend its execution and gave cpu-memory to other thread.
  • it will continue when other thread completed or gave its cpu memory too.

delay(timeInMillis)

  • this is suspend function.
  • take time as number in Millisecond.
  • it will pause execution for given time.
  • After that continue execution from there

join()

  • this method will block the execution until its instance coroutine( job ) finish all its execution
  • join will make sure that code in coroutine scope is complete executed before executing further statements in function or block

await()

  • it is method of deferred object
  • it will block execution until data returned in deferred object
 

cancel()

  • it used to explicitly kill our coroutine job based on some condition we need
  • It Throw CancellationException when used this method
 

isActive

  • it is Property of coroutine that return boolean value
  • it describe that current coroutine is Active or Cancelled
  • it used to add Cancellable execution in our coroutine
Example: If coroutine performing long running task and we cancel our job to end execution but coroutine job is too busy to notice this , so it will keep runnning until all execution
  • so need to add condition to make job cancelable
if( isActive ){ // Perform some long running task } if( some condition ){ job.cancel() }
 
 

Example 2 ( launch )

fun launchExample() { CoroutineScope(Dispatchers.IO).launch { printFollowers() } } private suspend fun printFollowers() { var fbFollowers = 0 var instaFollowers = 0 val job1 = CoroutineScope(Dispatchers.IO).launch { fbFollowers = getFbFollowers() } val job2 = CoroutineScope(Dispatchers.IO).launch { instaFollowers = getInstaFolowers() } job1.join() job2.join() Log.d(TAG, "FB- $fbFollowers , Insta = $instaFollowers") } private suspend fun getFbFollowers(): Int { delay(1000L) return 59 } private suspend fun getInstaFolowers(): Int { delay(1000L) return 113 }
 

Example - 2 ( async )

fun asyncExample() { CoroutineScope(Dispatchers.IO).launch { printFollowers2() } } private suspend fun printFollowers2() { val fbFollowers = CoroutineScope(Dispatchers.IO).async { getFbFollowers() } val instaFollowers = CoroutineScope(Dispatchers.IO).async { getInstaFolowers() } Log.d(TAG, "FB- ${fbFollowers.await()} , Insta = ${instaFollowers.await()}") } private suspend fun getFbFollowers(): Int { delay(1000L) return 59 } private suspend fun getInstaFolowers(): Int { delay(1000L) return 113 }
 
 

Final Example Using Both ( launch and async )

  • It will reduce time and line of code to execute and fetch same result as above
fun launchExample() { CoroutineScope(Dispatchers.IO).launch { finalPrintFollowers() } } private fun finalPrintFollowers() { CoroutineScope(Dispatchers.IO).launch { // Parent job val fb = async { getFbFollowers() } // child job 1 val insta = async { getInstaFolowers() } // child job 2 // if parent job dies all its child job will die automatically Log.d(TAG, "FB- ${fb.await()} , Insta = ${insta.await()}") } } private suspend fun getFbFollowers(): Int { delay(1000L) return 59 } private suspend fun getInstaFolowers(): Int { delay(1000L) return 113 }
 
 

Example of Hierarchical Structure Coroutine

  • In this one Coroutine can contain multiple coroutine in its scope
  • Main Coroutine called “ Parent Job ” and sub Coroutine called “ Child Job ’
  • all Child Job inherits its parent Job coroutineContext
  • we can explicitly change coroutine context of Our child job if we need
  • Note: IF Parent Job dies ( cancel() method called ) then all its sub child will automatically dies (exited without completed it execution )
// ... CoroutineScope(Dispatchers.Main).launch { execute() } // ... suspend fun execute(){ val parentJob = CoroutineScope(Dispatchers.Main).launch{ Log.d(TAG, "Parent Job Started") val childJob = launch{ Log.d(TAG, "Child Job Started") delay(5000) Log.d(TAG, "Child Job ended") } delay(3000) Log.d(TAG, "Parent Job ended") } // means if our coroutine has any uncompleted child job then it can't reach its complete state parentJob.join() Log.d(TAG, "Parent Job Completed") }
 
 
 
Kotlin Coroutine WithContext & runBlocking Functions | CheezyCode Hindi
Learn withContext Coroutine Builder and runBlocking function in Kotlin Coroutines. withContext is used for context switching in coroutines environment. You can use it to switch coroutine context from background thread to main thread. It is useful for updating UI from background coroutine. runBlocking is another coroutine builder that is used to block the thread to keep the coroutines alive. Coroutines by default does not block the thread. runBlocking is used to block the thread. This function is useful for executing test cases. Learn all about this in Hindi. Topics covered - 1. What is withContext function in Kotlin coroutines in Hindi 2. Learn what is runBlocking in Kotlin Coroutines 3. Complete Coroutines Practical Examples Kotlin Coroutines Playlist- https://www.youtube.com/playlist?list=PLRKyZvuMYSIN-P6oJDEu3zGLl5UQNvx9y Complete Android Architecture Components Playlist - https://www.youtube.com/playlist?list=PLRKyZvuMYSIO0jLgj8g6sADnD0IBaWaw2 Beginner series in Android Playlist (Hindi) - https://www.youtube.com/playlist?list=PLRKyZvuMYSIN9sVZTfDm4CTdTAzDQyLJU Kotlin Beginners Tutorial Series - https://www.youtube.com/playlist?list=PLRKyZvuMYSIMW3-rSOGCkPlO1z_IYJy3G For more info - visit the below link http://www.cheezycode.com We are social. Follow us at - Facebook - http://www.facebook.com/cheezycode Twitter - http://www.twitter.com/cheezycode Instagram - https://www.instagram.com/cheezycode/
Kotlin Coroutine WithContext & runBlocking Functions | CheezyCode Hindi

withContext(Dispatchers.(IO/Main/Default)){ … }

  • It is Mainly Used for Context Switching with blocking manner
  • if We are executing some task on IO thread and then we need to perform task on Main thread in between execution than we can use it
private suspend fun executeTask(){ Log.d(TAG, "Before") withContext(Dispatchers.IO){ delay(1000) Log.d(TAG, "Inside") } Log.d(TAG, "After") }
 
Example
  • if we want to perform some api request call on IO thread and want to do some change / update in our UI then we can use withContext(){} and change Coroutine Thread to Dispatchers.Main and block execution until withContext block execution completd.
  • after that it will automaticaly continue its execution again in IO thread
  • IT is an Suspend function
 
 

runBlocking{ … }

  • It is also an Coroutine Builder
  • as its name suggest it will block current Thread until all its child are thread completed
  • It is used to block JVM from exiting until all task are completed in coroutine otherwise JVM exits and coroutine block also dies without executing fully
  • Mainly used in Console Application and Android Test Cases where Thread dies automatically
  • Not Much use in android applications because Android thread runs continuously.

Examples:-

import kotlinx.coroutines.* fun main() = runBlocking{ launch{ delay(1000L) print("World") } print("Hello ") } main()
  • launch is a coroutine builder
  • delay is a special suspending function. It suspends the coroutine for a specific time.
  • runBlocking is also a coroutine builder that bridges the non-coroutine world of a regular fun main() and the code with coroutines inside runBlocking { ... } <font color='red'>Note:-</font>
  • go to project structure > select library > new (+) > from maven
  • when dialog box open paste this dependency and apply
jetbrains.kotlinx.coroutines.core

suspend Function

  • suspend function can only be called from coroutine scope
 
runBlocking { launch { doWorld() } println("Hello") } suspend fun doWorld() { delay(1000L) println("World!") }
  • In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the coroutineScope builder.
  • It creates a coroutine scope and does not complete until all launched children complete.
  • runBlocking and coroutineScope builders may look similar because they both wait for their body and all its children to complete.
  • The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.
  • You can use coroutineScope from any suspending function. For example, you can move the concurrent printing of Hello and World into a suspend fun doWorld() function:
runBlocking{ doWorld() } suspend fun doWorld() = coroutineScope{ launch{ delay(1000) println("World!") } println("Hello") }

A coroutineScope builder can be used inside any suspending function to perform multiple concurrent operations.

Let's launch two concurrent coroutines inside a doWorld() suspending function:
runBlocking { doWorld() // wait until other thread finish println("Done") } suspend fun doWorld() { coroutineScope { launch { delay(1000L) println("World 1") } launch { delay(1000) println("World 2") } println("Hello ") } } val job = launch { // launch a new coroutine and keep a reference to its Job delay(1000L) println("World!") } println("Hello") job.join() // wait until child coroutine completes println("Done")
If you write the same program using threads (remove runBlocking, replace launch with thread, and replace delay with Thread.sleep), it will consume a lot of memory. Depending on your operating system, JDK version, and its settings, it will either throw an out-of-memory error or start threads slowly so that there are never too many concurrently running threads.
 

ViewModelScope

 
  • It is an Coroutine Scope attached with your View Models.
  • Coroutine Inn this scope will be cancelled automatically when viewModel is cleared.
  • We don’t need to manually cancel the coroutines.
 
 
 

LifeCycleScope

 
  • It is an Coroutine Scope attached with Lifecycle ( Activity / Fragment )
  • Coroutine Inn this scope will be cancelled automatically when lifecycle is destroyed.
  • We don’t need to manually cancel the coroutines.