Kotlin coroutines enable you to write clean, simplified asynchronous code that keeps your app responsive while managing long-running tasks such as network calls or disk operations.
Coroutines, a very efficient and complete framework to manage concurrency in a more effective and simple way. It simplify your code by reducing the need for callbacks .
1. Features
- Lightweight : You can run many coroutines on a single thread , Coroutines are not thread instead they are like jobs inside thread
- Fewer memory leaks : Use structured concurrency to run operations within a scope.
- Built-in cancellation support : When using custom co-coroutine scope you can stop or cancel the execution at any point of time and cancellation is propagated automatically through the running coroutine hierarchy.
- Jetpack integration : Many Jetpack libraries provide full coroutines support , which will simply your code with less boiler plate code . Use Kotlin coroutines with Architecture components For ViewModelScope , LifecycleScope , liveData When using room peristense functions and working with networking like retrofit .
2. Coroutine Suspend and Resume
To handle long running tasks , we will park tasks in worker thread by suspending from current main thread and then using callback will resumes to caller which is main thread , Coroutines build upon regular functions by adding two operations to handle long-running tasks .
By just prefixing suspend keyword to a fuction , when that function is called it pauses the execution of the current coroutine, saving all local variables. and resumes from the place where it was suspended.
coroutineScope.launch(Dispatchers.Main) { Log.d(TAG, "Inside Thread ${Thread.currentThread().name}") val responseData = fetchData() displayData(responseData); }
In this example, fetchData() called on the main thread but it suspends the coroutine before it starts the network request. When the network request completes , fetchData() resumes the suspended coroutine instead of using a callback to notify the main thread.
3. Start a coroutine
You can start coroutines in two ways :
a. launch : starts a new coroutine and doesn't return the result to the caller , It is like fire and forgot .
b. async : starts a new coroutine and allows you to return a result with a suspend function called await, It is like launch and return result .
4. Dispatcher
Dispatcher is used to Decide which thread the corotuine should execute .
To specify where the coroutines should run, Kotlin provides three dispatchers that you can use:
- Dispatchers.Main : Execute Coroutine on Main Android Thread , This is used for performing quick UI Update .
- Dispatchers.IO : Execute Coroutine outside outside of the main thread , This is mainly used for performing network and disk operations .
- Dispatchers.Default : Execute Coroutine outside outside of the main thread , This is used to perform CPU-intensive work for ex image processing , performing sort / search on a list .
5. CoroutineScope
A CoroutineScope keeps track of any coroutine it creates using launch or async . The on going co-coroutine execution can be cancelled using by calling scope.cancel() at any point of time .
If you need to create your own CoroutineScope to control the lifecycle of coroutines in a particular
Creating Custom CoroutineScope gives the control of the lifecycle of co-coroutine in your application ,
Example In activity onCreate create custom co-coroutine and in ondestroy cancel the co-coroutine .
6. CoroutineContext
CoroutineContext has set of elements that defines the behavior of co-coroutine.
- Job : Each coroutine that you create with launch or async returns a Job instance that uniquely identifies the coroutine and manages its lifecycle
- Coroutine dispatcher : Above mentioned .
- Coroutine Exception : Handles uncaught exceptions.
Lets see how to create corotuine scope by pass passing courtine context .
//1. Job private var job = Job() //2. coroutine Exception val handler = CoroutineExceptionHandler { _, exception -> Log.d(TAG, "$exception handled !") } //3. coroutine context val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler //4. coroutine scope private val coroutineScope = CoroutineScope(coroutineContext)
7. Coroutine parallel and serial execute
a) Async and Await : If multiple tasks have to happen in parallel and final result depends on completion of all of them, then use async. You should use async/await when you want to execute multiple tasks concurrently .
val resultOne = coroutineScope.async(Dispatchers.IO) { funtion1()} val resultTwo= coroutineScope.async(Dispatchers.IO) {function2() } val result=resultOne.await()+resultTwo.await()
b) withContext : For returning the result of a single task, use withContext.
val result= withContext(Dispatchers.IO) { function1() }
Lets see sample example .
1. XML Layout
file : activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/fetchdataBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fetch Data" /> <TextView android:id="@+id/resultText" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
1. Activity
- In this example we will make api request "https://api.github.com/users/tutorialsbuzz" and set the response to TextView .
- In MainActivity On Click of Button launch a co-coroutine by mentioning Main dispatcher and call fetchData() .
- Now inside fetchData function launch another coroutine using async function by mentioning I/O dispatcher which returns Deferred<String?> .
- The from the returned Deferred<String?.> call await() and pass it to displayData() .
file : MainActivity.kt
package com.tutorialsbuzz.coroutinesdemo class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.simpleName.toString() private var job = Job() //coroutine Exception val handler = CoroutineExceptionHandler { _, exception -> Log.d(TAG, "$exception handled !") } //coroutine context val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler //coroutine scope private val coroutineScope = CoroutineScope(coroutineContext) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) fetchdataBtn.setOnClickListener { coroutineScope.launch(Dispatchers.Main) { Log.d(TAG, "Inside Thread ${Thread.currentThread().name}") //async returning Deferred < dataType > val responseData = fetchData() displayData(responseData.await()); } } } fun fetchData(): Deferred<String?> { val loginUrl = "https://api.github.com/users/tutorialsbuzz" return coroutineScope.async(Dispatchers.IO) { val url = URL(loginUrl) val urlConnection = url.openConnection() as HttpURLConnection val inputAsString = urlConnection.inputStream?.bufferedReader().use { it?.readText() } inputAsString } } fun displayData(data: String?) { resultText.setText(data) } }
In the above example lets see another way of writing fetchData and calling it from main coroutine dispatcher
1. async returning dataType
coroutineScope.launch(Dispatchers.Main) { val responseData = fetchData() displayData(responseData); } suspend fun fetchData(): String? { val loginUrl = "https://api.github.com/users/tutorialsbuzz" val response = coroutineScope.async(Dispatchers.IO) { Log.d(TAG, "Inside Thread ${Thread.currentThread().name}") val url = URL(loginUrl) val urlConnection = url.openConnection() as HttpURLConnection val inputAsString = urlConnection.inputStream?.bufferedReader().use { it?.readText() } inputAsString }.await() return response }
2. withContext returning dataType
coroutineScope.launch(Dispatchers.Main) { val responseData = fetchData() displayData(responseData); } suspend fun fetchData(): String? { val loginUrl = "https://api.github.com/users/tutorialsbuzz" return withContext(Dispatchers.IO) { val url = URL(loginUrl) val urlConnection = url.openConnection() as HttpURLConnection val inputAsString = urlConnection.inputStream?.bufferedReader().use { it?.readText() } inputAsString } }
No comments:
Post a Comment