Kotlin Coroutines Tutorial for Android — Start using Coroutines in Your App
This Story is from the Kotlin- Series, now we are creating one more coroutines medium for more clarification. For begginer please follow my previous link
Kotlin Coroutines in an Android app — the recommended way of managing background threads that can simplify code by reducing the need for callbacks. Coroutines are a Kotlin feature that converts async callbacks for long-running tasks, such as database or network access, into sequential code.
Why use Coroutine?
We already had number of tools for async programming, then why we need Coroutine? Well this is a fair question that should be asked. We have RxJava, AsyncTask, Threads etc. Then why I should focus on learning a new concept?
But if you are using the above mentioned things, then you already know that how difficult it is to use RxJava correctly. The learning curve of RxJava is also too much. AsyncTask can easily introduce memory leaks in our app. Managing threads are another pain.
But with Coroutines all these shortcomings are fixed. Trust me guys, once you will start using it, you will understand how easy it is to learn and write.
On Android, coroutines are a great solution to two problems:
- Long running tasks are tasks that take too long to block the main thread.
- Main-safety allows you to ensure that any suspend function can be called from the main thread.
Let’s dive in to each to see how coroutines can help us structure code in a cleaner way!
Long running tasks
Fetching a webpage or interacting with an API both involve making a network request. Similarly, reading from a database or loading an image from disk involve reading a file. These sorts of things are what I call long running tasks — tasks that take far too long for your app to stop and wait for them!
In order to perform a network request off the main thread, a common pattern is callbacks. Callbacks provide a handle to a library that it can use to call back into your code at some future time. With callbacks, fetching developer.android.com might look like this:
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.com") { result ->
show(result)
}
}
}
Even though get
is called from the main thread, it will use another thread to perform the network request. Then, once the result is available from the network, the callback will be called on the main thread. This is a great way to handle long running tasks, and libraries like Retrofit can help you make network requests without blocking the main thread.
Using coroutines for long running tasks
Coroutines are a way to simplify the code used to manage long running tasks like fetchDocs
. To explore how coroutines make the code for long running tasks simpler, let’s rewrite the callback example above to use coroutines.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
// look at this in the next section
suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}
Coroutines build upon regular functions by adding two new operations. In addition to invoke (or call) and return, coroutines add suspend and resume.
- suspend — pause the execution of the current coroutine, saving all local variables
- resume — continue a suspended coroutine from the place it was paused
This functionality is added by Kotlin by the suspend keyword on the function. You can only call suspend functions from other suspend functions, or by using a coroutine builder like launch
to start a new coroutine.
Suspend and resume work together to replace callbacks.
In the example above, get
will suspend the coroutine before it starts the network request. The function get
will still be responsible for running the network request off the main thread. Then, when the network request completes, instead of calling a callback to notify the main thread, it can simply resume the coroutine it suspended.
Looking at how fetchDocs
executes, you can see how suspend works. Whenever a coroutine is suspended, the current stack frame (the place that Kotlin uses to keep track of which function is running and its variables) is copied and saved for later. When it resumes, the stack frame is copied back from where it was saved and starts running again. In the middle of the animation — when all of the coroutines on the main thread are suspended, the main thread is free to update the screen and handle user events. Together, suspend and resume replace callbacks. Pretty neat!
When all of the coroutines on the main thread are suspended, the main thread is free to do other work.
Even though we wrote straightforward sequential code that looks exactly like a blocking network request, coroutines will run our code exactly how we want and avoid blocking the main thread!
Next, let’s take a look into how to use coroutines for main-safety and explore dispatchers.
Main-safety with coroutines
In Kotlin coroutines, well written suspend functions are always safe to call from the main thread. No matter what they do, they should always allow any thread to call them.But, there’s a lot of things we do in our Android apps that are too slow to happen on the main thread. Network requests, parsing JSON, reading or writing from the database, or even just iterating over large lists. Any of these have the potential to run slowly enough to cause user visible “jank” and should run off the main thread.
Using suspend
doesn’t tell Kotlin to run a function on a background thread. It’s worth saying clearly and often that coroutines will run on the main thread. In fact, it’s a really good idea to use Dispatchers.Main.immediate when launching a coroutine in response to a UI event — that way, if you don’t end up doing a long running task that requires main-safety, the result can be available in the very next frame for the user.
Coroutines will run on the main thread, and suspend does not mean background.
To make a function that does work that’s too slow for the main thread main-safe, you can tell Kotlin coroutines to perform work on either the Default
or IO
dispatcher. In Kotlin, all coroutines must run in a dispatcher — even when they’re running on the main thread. Coroutines can suspend themselves, and the dispatcher is the thing that knows how to resume them.
To specify where the coroutines should run, Kotlin provides three Dispatchers you can use for thread dispatch.
+-----------------------------------+
| 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 |
+-----------------------------------+
* Room will provide main-safety automatically if you use suspend functions, RxJava, or LiveData.
** Networking libraries such as Retrofit and Volley manage their own threads and do not require explicit main-safety in your code when used with Kotlin coroutines.
To continue with the example above, let’s use the dispatchers to define the get
function. Inside the body of get
you call withContext(Dispatchers.IO)
to create a block that will run on the IO
dispatcher. Any code you put inside that block will always execute on the IO
dispatcher. Since withContext
is itself a suspend function, it will work using coroutines to provide main safety.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
// Dispatchers.Main
suspend fun get(url: String) =
// Dispatchers.Main
withContext(Dispatchers.IO) {
// Dispatchers.IO
/* perform blocking network IO here */
}
With coroutines you can do thread dispatch with fine-grained control. Because withContext
lets you control what thread any line of code executes on without introducing a callback to return the result, you can apply it to very small functions like reading from your database or performing a network request. So a good practice is to use withContext
to make sure every function is safe to be called on any Dispatcher
including Main
— that way the caller never has to think about what thread will be needed to execute the function.
In this example, fetchDocs
is executing on the main thread, but can safely call get
which performs a network request in the background. Because coroutines support suspend and resume, the coroutine on the main thread will be resumed with the result as soon as the withContext
block is complete.
Well written suspend functions are always safe to call from the main thread (or main-safe).
It’s a really good idea to make every suspend function main-safe. If it does anything that touches the disk, network, or even just uses too much CPU, use withContext
to make it safe to call from the main thread. This is the pattern that coroutines based libraries like Retrofit and Room follow. If you follow this style throughout your codebase your code will be much simpler and avoid mixing threading concerns with application logic. When followed consistently, coroutines are free to launch on the main thread and make network or database requests with simple code while guaranteeing users won’t see “jank.”
Performance of withContext
withContext
is as fast as callbacks or RxJava for providing main safety. It’s even possible to optimize withContext
calls beyond what’s possible with callbacks in some situations. If a function will make 10 calls to a database, you can tell Kotlin to switch once in an outer withContext
around all 10 calls. Then, even though the database library will call withContext
repeatedly, it will stay on the same dispatcher and follow a fast-path. In addition, switching between Dispatchers.Default
and Dispatchers.IO
is optimized to avoid thread switches whenever possible.
Examples overview
Step 1. Create new Activity name it as per your wish :
Using androidx for creating UI.In this activity view we have one Button and one TextView. On button click we are performing coroutine
class MainActivity : AppCompatActivity() {
private val RESULT_1 = "Result #1"
private val RESULT_2 = "Result #2"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
// IO,MAin,Default
CoroutineScope(IO).launch {
fakeApiRequest()
}
}
}
Suspending functions are introduced with Coroutines and allow us to start the function from where it left off. This aids further in memory optimizations of the CPU and multi-tasking.Unlike blocking a thread, suspending functions are less expensive and do not require a context switching.
In suspend function we can performing long running task and we can also perform multiple task.
private suspend fun fakeApiRequest() {
val result1 = getResult1FromApi()
println("debug:$result1")
setTextOnMainThread(result1)
val result2=getResult2FromApi()
setTextOnMainThread(result2)
}
// suspend can be asynchronous
private suspend fun getResult1FromApi(): String {
logThread("getResult1FromApi")
delay(1000)
// Thread.sleep()
return RESULT_1
}
private suspend fun getResult2FromApi():String{
logThread("getResult2FromApi")
delay(1000)
return RESULT_2
}
private fun logThread(methodName: String) {
println("Debug: ${methodName}:${Thread.currentThread().name}")
}
Than, updating the value to Main-Thread
private fun setNewText(input:String){
val newText=normaltxt.text.toString()+"\n$input"
normaltxt.text=newText
}
private suspend fun setTextOnMainThread(input: String){
withContext(Main){
setNewText(input)
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.ui.MainActivity">
<TextView
android:id="@+id/normaltxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login"
android:textAllCaps="true"
android:textColor="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.464"
app:layout_constraintVertical_bias="0.051"/>
<Button
android:text="@string/submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button"
android:textColor="#FFF"
android:background="@color/colorPrimaryDark"
app:layout_constraintTop_toBottomOf="@+id/normaltxt" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.936"/>
</androidx.constraintlayout.widget.ConstraintLayout>
It takes a lot of effort to make these courses for you. So I request you all that please clap clap clap 👏👏👏 as many times as you can.