Understanding Asynchronous Programing in Kotlin: Coroutines

Understanding Asynchronous Programing in Kotlin: Coroutines

Hello Hashnoders!

A bit about myself

I'm Nishant Kumar, doing my stuff in Android Studio. Today I am writing a blog about Kotlin Coroutines. I am trying to make sense of Coroutines for Beginners and somewhat even for myself.

Why I wrote a Blog

I heard Kotlin Coroutines are great and I have used them in many projects, and but I have only know about some basic things about them and was using them without having full understanding and i was just using them make my projects working. So I thought I should read about them more and try to make sense of it.

So First I thought about reading Official Kotlin's official Documentation for Coroutines. But I hesitated to read Documentations as they sometimes fell over whelming , I consider this my Bad Habit. May be others also do the same.

Me be like -

Whenever I want to learn about something new or trying to solve a problem. I directly try to go look for the solution or directly read some article. This sometimes gives incomplete knowledge of the tech I want to learn and yeah got solution for my errors. But this creates a problem that I don't understand the actual working of that thing whether its a library or some framework.

So I am trying to break this habit. Reading documentation won’t make you a pro-coder overnight, but it will have some nice roll-on effects. I tried reading about Coroutines and thinking about making some notes about them.. But then I thought I should try to write a blog about it on hashnode instead of making simple notes. I might make some mistakes so bear with me. I also added some code snippets . So here is my work.

As Megumi Fushiguro said -

Anyway enough anime. Lets start with Threads

Thread

Let's first talk about Threads. A thread is a single sequential flow of control within a program. A thread describes in which context this sequence of instructions should be executed.

Why is threading actually important for Android Apps

In a normal program all instructions are generally executed in a single thread which is called Main Thread, if you don’t use multi-threading of course. Any block of code we want to run will on our Main Thread. How ever that can lead to some problems. When our app performs complex tasks which are based on User Interaction which are running on a single thread, will give us performance issues. When everything happens in a single thread then that also mean that the UI needs to be updated in that same thread and your UI updates in Android many many times a seconds. And because of that they gives us a illusion that the View are actually moving.

updateUI()
var y = 3
updateUI()
println("Hello Main Thread")
updateUI()

So it means our own instructions are actually mixed with our app’s UI updation instructions. let's say you need to do a network call which you need to do quite often on Android, that takes some time until it gets the answer. So if you would do that on the main thread then that would mean that you would freeze your UI as long as you're waiting for that answer because all instructions in the main thread are executed one after another so no UI update instructions can happen while you're waiting for that Network call answer

instantitateViews()
updateUI()

doNetworkCall()

updateUI()
println(""Hello Main Thread)
updateUI()

for that reason we start a separate thread for that to not block other important threads

instantitateViews()
updateUI()

Thread {
    doNetworkCall()
}.start()

updateUI()
println(""Hello Main Thread)
updateUI()

so as you can see multi-threading is indeed super important because we need it for very common problems but why do I tell you all this if this article is about Coroutines and the simple answer to that is because they can do all the stuff threads can do but even more and they're even more efficient

Multihreading Uses - Network Calls, Database Operations, Complex Calculations, etc.

What are Coroutines ?

A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages.

"One can think of a coroutine as a light-weight thread. Like threads, coroutines can run in parallel, wait for each other and communicate. The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. True threads, on the other hand, are expensive to start and keep around. A thousand threads can be a serious challenge for a modern machine."

What makes Coroutines different from Threads

  • Executed within a thread , launching multiple coroutines in a single thread
  • Coroutines are suspendable i.e., suspend + resume
  • They can switch their context and not bound to a particular thread

Starting our First Coroutine

We need a coroutines scope to achieve this.. I am using GlobalScope, that lives as long our application lives. But in practice we don’t always need such a coroutine scope. We' further talk about this.

onCreate(){

    GlobalScope.launch {
        Log.d(TAG, "This is Thread 1")
    }
    Log.d(TAG, "This is Thread 2")
}

Output :- This is Thread 1
             This is Thread 2

Suspend Functions

A suspending function is simply a function that can be paused and resumed at a later time. These functions can be executed within another suspend function or inside of a coroutine scope.


 onCreate() {

    GloablaScope.launch{
        val networkCallAnswer = doNetworkCall()
        val networkCallAnswer2 = doNetworkCall2()

        Log.d(TAG, networkCallAnswer)
        Log.d(TAG, netowrkCallAnswer2)        
    }
     suspend un doNetworkCall(): String {
        delay(3000L)
        return "Answer 1"
    }


    suspend un doNetworkCall2(): String {
        delay(3000L)
        return "Answer 2"
    }
}

output : Answer1
                 Answer2

After 6 secs we get the answer, both calls were 
add up as they were called in same coroutine

Coroutine Contexts

Coroutines are in generally are started in a specific context and the context will decide in which thread our coroutine will be started in.

Like say we use GlobalScope.launch { …. } to start our coroutine. Here we can pass a dispatcher as parameter in launch function which will tell our coroutine start in which thread.

There are following types of Dispatchers:

  1. Dispatchers.Main : It will start a coroutine in a Main Thread that is useful when we need to do UI operations from within the coroutine because we can only change UI from Main Thread.
  2. Dispatchers.IO : It’s use for all kinds of data operations such as networking, writing to databases or reading/writing to files.
  3. Dispatchers.Default : It’s used for complex calculations that will block Main Thread such as sorting a list with large number elements.
  4. Dispatchers.Unconfined : As name suggests , its not confined to a specific thread. So if we launch a coroutine in unconfined that calls a suspend function, it will stay in the same thread that the suspend function resumed.

Switching Contexts

The useful thing about coroutine contexts is that we can easily switch them within a coroutine using withContext() function.

GlobalScope.launch(Disptacher.IO) {
    //Do network Call Here in Background Thread
    withContext(Disptacher.Main) {
        // Do UI Related work here
    }    
}

Blocking Main Thread and start a coroutine

Its can be used when we don’t necessarily need coroutine behavior but still want to call a suspend function. It can be achieved using runBlocking() function. It provides us a coroutineContext.

GlobalScope.launch(Disptacher.IO) {
    //It will run asynchronously and don't block the Main Thread.
}

runBlocking {
    //Do network Call Here in Background Thread
    withContext(Disptacher.Main) {
        // IT will run synchronously and block the Main Thread.
        // suspend function here
    }    
}

We can also run new coroutines inside runBlocking and that will run asynchronously. Below coroutines block 1 and 2 will be executed parallelely.

runBlocking {
    launch(Dispatchers.IO) {
         //This is coroutines 1
    }
    launch(Dispatchers.IO) {
        //This is coroutines 2
    }    
}

Jobs, Waiting, Cancelation

When we launch a coroutine , it returns a Job

So we can use a join() function to block our thread and wait until the coroutine is finished.

val job = GlobalScope.launch(Dispatchers.Default) {
    // Do some work here
}
runBlocking {
    job.join() // It will block the thread until job is finished
}

Also , if we want to stop our coroutine, we can use cancel() on our job and it won’t go on.

runBlocking {
    job.cancel() // It will block the thread until job is finished
}

Sometimes , our coroutine become so busy in calculations that there is no time to check for cancellation, so we have to handle it properly. We can check manually if the coroutine is still active or not while its executing using isActive.

GlobalScope.launch(Disptacher.IO) {
    // we doing some task and want to check whether our coroutine scope is still active
    if(isActive) {
        //Do our complex calculations task 
    }
    // other wise stop the tasks
}
runBlocking {
    job.cancel()
}

So here our cancellation will work fine.

Use case for cancelling a coroutine can be a network that takes long time and we want to cancel it at some point. To achieve this behavior we have a separate useful suspend function

withTimeout( time in ms)

This function automatically cancel the job when the time limit is over, without us need to cancel it manually.

GlobalScope.launch(Dispatchers.Default){
    withTimeut(3000L){
        // Do our Task here and will automatically cancelled in 3 secs    
    }
}

So here we creating a coroutine and let it work for 3 secs and cancelling it after timeout.

Running Tasks Parallelly

Generally two coroutines launched are executed synchronously, but when sometimes we want to run them to be executed parallelly. For example we want to hit two network call , we don’t want the first one to complete and then execute the second one. Instead we want to run them parallelly and get both result at the end. So to achieve this behavior we can use job and its .join() method

Generally two coroutines launched are executed synchronously, but when sometimes we want to run them to be executed parallelly. For example we want to hit two network call , we don’t want the first one to complete and then execute the second one. Instead we want to run them parallelly and get both result at the end. So to achieve this behavior we can use job and its .join() method

Its one way to achieve this, but approach is not a good practice.

To achieve this functionality we have async {} and await(). Async is similar to launch{} , but it actually return a Deferred which we can use to get our result. We then use await() to get our data from async block. The await() will block our thread until we get our data from async block.

GlobalScope.launch {
            val data1 =  async { task1() }
            val data2 = async { task2() }
            Log.d("Task2->", data1.await())
            Log.d("Task2->", data2.await())
        }

So whenever we want to do some task asynchronously and want a result back , we should use async{} instead of launch{} .

LifecycleScope and ViewModelScope…

In above code examples , we’re using Globalscope to start a coroutine scope that lives as long our application lives. But in practice we don’t always need such a coroutine scope. So , it wil be a bad practice to use a Globalscope in our application.

Lets consider we use Globalscope in our MainActivity Class to use some resources to perform tasks

and when our work is completed and we navigate to some other activity and destroy our MainActivity. But as we using Globalscope which outlives the scope of a Activity and resources occupied by this scope will not be freed by Garbage Collector and it will lead to memory leaks.

class MainActivity {

    onCreate(){...
            GlobalScope.launch {
                //Use resources and perform operations here..
                //We navigate to Other Activity and destroy this activity
                //This scope will lives till the application lives.        
            }        
    }
}

So what will be the answer to that ?

In Android we have two predefined scopes that we use to launch a coroutine scope -

  1. lifecycleScope
  2. viewModelScope

We need to add these dependencies in our app gradle to use these:

We need to add these dependencies in our app gradle to use these:
  1. lifescycleScope - This coroutine scope sticks to the lifecycle of a Activity and stay alives till our Activity is alive.
class MainActivity {

    onCreate(){...
            lifecycleScope.launch {
                //Use resources and perform operations here..
                //When we navigate to Other Activity and destroy this activity,
                //This coroutine scope will also be destoryed 
            }        
    }
}
  1. viewModelScope - - This coroutines scope is used in our ViewModel class and stays till our that instance of viewmodel lives.
class MainViewModel: ViewModel() {

    suspend fun fetchData() = viewmodelScope.launch {
          //Fetch data
            //When our viewmodel is destoryed ,this scope will also be destroyed.
    }
}

Firebase and Coroutines

Yes , they work with firebase too. Generally when we try to get data from firestore , it uses callback functions to perform network calls asynchronously. So this sometimes creates a nested callback within each other it’s called callback-hell.

Example using callbacks

getUserData.onSuccessTask { data1 ->
    getUserData2.onSuccessTask { data2 ->
        getChats.onSuccessTask { messages ->
            // Here you can get your messages data ...
        }
    }
}

So, to prevent this we can use await() with our firestore calls.

Firebase with await() be like -

To use await() with firebase we have to add this dependency in our firebase.

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:version-here'

So, generally it works with firestore’s instance that returns Task, here how you achieve this

{ //CoroutineScope

        val userData1 = Firebase.firestore.collection("user").document("uid_2").get().await()
        val userData2 = Firebase.firestore.collection("user").document("uid_2").get().await()

        // So here we get userData1 and userData2 synchronously without callbacks using await()        
}

Conclusion 👏

There are many more topics in coroutines and I only some topics of it. I tried explaining some of them that I used often in my projects. So I hope you find this article helpful in some ways. And if you have any doubt feel free to reach out to me via LinkedIn Github

Feel free to share your opinions in comment section and leave a like and share if you want to. See you soon