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 aDeferred<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
CancellationExceptionwhen 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") }
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 toDispatchers.Mainand block execution untilwithContextblock 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.