Android Kotlin Coroutines For Beginners

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


Coroutines is recommended solution for asynchronous programming on Android .
  • 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() }

Both withContext and async can be used to get the result which is not possible with the launch.

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