└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Coroutines 2 | 3 | 4 | ## What exactly Coroutines are? 5 | 6 | |Co|Routines| 7 | |--|--| 8 | | Cooperation |Function | 9 | 10 | **It means that when functions cooperate with each other, we call it Coroutines.** 11 | 12 | For Example 13 | 14 | 15 | 16 | 17 | 18 | fun functionA(case: Int) { 19 | when (case) { 20 | 1 -> { taskA1() functionB(1) } 21 | 2 -> { taskA2() functionB(2) } 22 | 3 -> { taskA3() functionB(3) } 23 | 4 -> { taskA4() functionB(4) } 24 | } 25 | } 26 | 27 | 28 | 29 | fun functionB(case: Int) { 30 | when (case) { 31 | 1 -> { taskB1() functionA(2) } 32 | 2 -> { taskB2() functionA(3) } 33 | 3 -> { taskB3() functionA(4) } 34 | 4 -> { taskB4() } 35 | } 36 | } 37 | 38 | **Then, we can call the functionA as below:** 39 | 40 | functionA(1) 41 | Here, **functionA** will do the **taskA1** and give control to the **functionB** to execute the **taskB1**. 42 | 43 | Then, **functionB** will do the **taskB1** and give the control back to the **functionA** to execute the **taskA2** and so on. 44 | 45 | The important thing is that functionA and functionB are cooperating with each other. 46 | 47 | With Kotlin Coroutines, the above cooperation can be done very easily which is without the use of **when** or **switch** **case** which I have used in the above example for the sake of understanding. 48 | 49 | Now that, we have understood what are coroutines when it comes to cooperation between the functions. There are endless possibilities that open up because of the cooperative nature of functions. 50 | 51 | **Coroutines** are available in many languages. Basically, there are two types of Coroutines: 52 | 53 | - Stackless 54 | - Stackful 55 | 56 | Kotlin implements stackless coroutines — it means that the coroutines don’t have their own stack, so they don’t map on the native thread. 57 | 58 | ## **Why there is a need for Kotlin Coroutines?** 59 | - Fetch User from the server. 60 | - Show the User in the UI. 61 | 62 | 63 | 64 | fun fetchUser(): User { 65 | // make network call 66 | // return user 67 | } 68 | fun showUser(user: User) { 69 | // show user 70 | } 71 | fun fetchAndShowUser() { 72 | val user = fetchUser() 73 | showUser(user) 74 | } 75 | 76 | 77 | When we call the **fetchAndShowUser** function, it will throw the **NetworkOnMainThreadException** as the network call is not allowed on the main thread. 78 | 79 | There are many ways to solve that. A few of them are as follows: 80 | 81 | 1. **Using Callback:** Here, we run the fetchUser in the background thread and we pass the result with the callback. 82 | 2. **Using RxJava:** Reactive world approach. This way we can get rid of the nested callback. 83 | 3. **Using Coroutines:** Yes, coroutines. 84 | 85 | **We have introduced two things here as follows:** 86 | - **Dispatchers**: Dispatchers help coroutines in deciding the thread on which the work has to be done. There are majorly three types of Dispatchers which are as **IO, Default, and Main**. IO dispatcher is used to do the network and disk-related work. Default is used to do the CPU intensive work. Main is the UI thread of Android. In order to use these, we need to wrap the work under the **async** function. Async function looks like below. 87 | 88 | - **suspend**: Suspend function is a function that could be started, paused, and resume. 89 | - https://s3.ap-south-1.amazonaws.com/mindorks-server-uploads/suspend-coroutines.png 90 | Suspend functions are only allowed to be called from a coroutine or another suspend function. You can see that the **async** function which includes the keyword **suspend**. So, in order to use that, we need to make our function **suspend** too. 91 | 92 | So, the **fetchAndShowUser** can only be called from another suspend function or a coroutine. We can't make the onCreate function of an activity **suspend**, so we need to call it from the coroutines like below: 93 | 94 | 95 | 96 | override fun onCreate(savedInstanceState: Bundle?) { 97 | super.onCreate(savedInstanceState) 98 | GlobalScope.launch(Dispatchers.Main) { 99 | fetchAndShowUser() 100 | } 101 | } 102 | 103 | 104 | 105 | ## **Launch vs Async in Kotlin Coroutines** 106 | - `launch`: fire and forget 107 | - `async`: perform a task and return a result 108 | - `withContext` Like async but without `await()` 109 | 110 | **The thumb-rules:** 111 | - Use `withContext` when you do **not** need the parallel execution. 112 | - Use `async` only when you need the parallel execution. 113 | - Both `withContext` and `async` can be used to get the result which is not possible with the `launch`. 114 | - Use `withContext` to return the result of a single task. 115 | - Use `async` for results from multiple tasks that run in parallel. 116 | 117 | ## Scopes in Kotlin Coroutines 118 | 119 | Scopes in Kotlin Coroutines are very useful because we need to cancel the background task as soon as the activity is destroyed. 120 | 121 | **There are basically 3 scopes in Kotlin coroutines:** 122 | - Global Scope. 123 | - LifeCycle Scope. 124 | - ViewModel Scope. 125 | 126 | ## Exception Handling in Kotlin Coroutines 127 | 128 | **When Using launch** 129 | 130 | GlobalScope.launch(Dispatchers.Main) { 131 | try { fetchUserAndSaveInDatabase() 132 | // do on IO thread and back to UI Thread 133 | } 134 | catch (exception: Exception) { 135 | Log.d(TAG, "$exception handled !") 136 | } 137 | } 138 | 139 | 140 | Another way is to use a handler: 141 | 142 | 143 | val handler = CoroutineExceptionHandler { _, exception -> 144 | Log.d(TAG, "$exception handled !") 145 | } 146 | 147 | Then, we can attach the handler like below: 148 | 149 | GlobalScope.launch(Dispatchers.Main + handler) { 150 | fetchUserAndSaveInDatabase() 151 | // do on IO thread and back to UI Thread 152 | } 153 | **what if we want to make the network calls in parallel. We can write the code like below using `async`** 154 | 155 | launch { 156 | try { 157 | val usersDeferred = async { getUsers() } 158 | val moreUsersDeferred = async { getMoreUsers() } 159 | val users = usersDeferred.await() 160 | val moreUsers = moreUsersDeferred.await() 161 | } 162 | catch (exception: Exception) { 163 | Log.d(TAG, "$exception handled !") 164 | } 165 | } 166 | Here, we will face one problem, if any network error comes, the application will **crash!**, it will **NOT** go to the `catch` block. 167 | 168 | **To solve this, we will have to use the `coroutineScope` like below:** 169 | 170 | launch { 171 | try { 172 | coroutineScope { 173 | val usersDeferred = async { getUsers() } 174 | val moreUsersDeferred = async { getMoreUsers() } 175 | val users = usersDeferred.await() 176 | val moreUsers = moreUsersDeferred.await() 177 | } 178 | } 179 | catch (exception: Exception) { Log.d(TAG, "$exception handled !") 180 | } 181 | } 182 | 183 | Now, if any network error comes, it will go to the `catch` block. 184 | --------------------------------------------------------------------------------