Mobile Application

Fundamentals of Using Kotlin Coroutines in Android

Introduction

In this world, what do people expect from technology? We expect that all things should be done efficiently and quickly. This can be achieved by a procedure known as Multitasking.

Multitasking has 2 types of methods, like

  1. Operating systems manage the flow between operations.
  2. This second type is called Cooperative multitasking, Operations control changes by itself.

For that reason in the kotlin language, There is one concept called Coroutine

Coroutines were launched to Kotlin in version 1.3. Coroutine is basically a multitasking method. By use of this, we can perform multiple simultaneous processes. A coroutine is not a thread, a Coroutine is normally the same as a lightweight thread. 

In one thread, we can access the multiple coroutines. A coroutine can exchange the thread from one to another. But Sometimes, If the Programmer can’t use the coroutine in the proper way then it’ll create some issues. Like memory-related problems, Unnecessary problems will be created. 

Before starting the Demo of the Coroutine, you should have to clear some coroutine-related topics. Coroutine has some topic which is described below: Scopes, Dispatchers, and Suspend Functions.

1. Coroutine Scopes

The scope of Kotlin coroutines can be defined as the constraints within which the coroutines are executed. Scopes help in modelling the lifecycle of coroutines. Kotlin coroutines have three basic scopes:

  • Global Scope
  • LifeCycle Scope
  • ViewModel Scope

We can declare the dispatches in the Scope, at the end of all tasks we have to close that coroutine operation in scope. For this purpose, we can use the Scopes. If we destroy the “Scope” then all the running tasks of that particular scope will also destroy with that Scope.

2. Coroutine Dispatchers

Dispatchers are given the context of the coroutine code. So coroutine finds the thread where they are executed. Dispatchers have 4 types:

  • Main Dispatchers
  • IO Dispatchers
  • Default Dispatchers
  • Unconfined Dispatcher

1. Main Dispatchers

Main dispatchers execute the coroutine on the main thread. The main dispatchers do most of the work on UI. It’s also known as a UI thread.

2. IO Dispatchers

IO Dispatchers initiate the coroutine on the IO thread, which helps to perform all data operations such as the internet, either reading or writing from the database, reading or writing to files, and so on. Data retrieval from a database is an IO operation performed on the IO thread.

3. Default Dispatchers

Default dispatchers work on the default thread. When we want to do intricate and prolonged calculations that may cause the main thread to lag and the user display to become inaccessible, that’s why we should use this dispatcher.

4. Unconfined Dispatcher

Unconfined Dispatcher does not work on any particular thread. without requiring any particular threading strategy, permits the coroutine to restart in whatever thread is being utilized by the associated suspending function.

3. Suspended Function

A function called suspend has three possible states: begun, stopped, and resumed. One of the most crucial things to keep in mind regarding the suspend functions is that they may only be invoked from another suspension function or a coroutine.

Let’s Start The Demo

We are creating one demo project to better understand the coroutine’s work. In this project, we have 2 counters which increase from 1 to 10. the 1st counter will work at a 1-second delay. On the other hand, the 2nd one will work at a 3-second delay.

At the bottom, there are 2 buttons, one for starting the process and the second one for stopping the counting. After the process there is one pop-up toast will be generated. So this is the work of our demo.

First of all, you have to start with setting the dependency and Gradle settings. Which are shown below:

//build.gradle

plugins {
   id 'com.android.application'
   id 'org.jetbrains.kotlin.android'
}
android {
   compileSdk 32
   defaultConfig {
       applicationId "com.example.highlevelcoroutine"
       minSdk 22
       targetSdk 32
       versionCode 1
       versionName "1.0"
       testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
   }
   buildFeatures{
       dataBinding true
   }
   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
   kotlinOptions {
       jvmTarget = '1.8'
   }
}

dependencies {
   implementation 'androidx.core:core-ktx:1.7.0'
   implementation 'androidx.appcompat:appcompat:1.5.1'
   implementation 'com.google.android.material:material:1.6.1'
   implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
   testImplementation 'junit:junit:4.13.2'
   androidTestImplementation 'androidx.test.ext:junit:1.1.3'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
   implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
}

If there is some dependency that will be deprecated then you can check from android’s documentation.

After the dependency work, We will start coding.

First create the main activity and its layout. So we create on UI XML file and give the name like activity_main.xml, Its code is below:

//activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:weightSum="3"
       android:orientation="vertical"
       tools:context=".MainActivity">

       <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:gravity="center"
           android:orientation="horizontal"
           android:background="@android:color/holo_blue_dark">

           <TextView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginEnd="20dp"
               android:text="@string/the_counter_first_text"
               android:textSize="32sp"
               android:textColor="@color/white" />

           <TextView
               android:id="@+id/the_first_counter_tv"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="0"
               android:textSize="30sp"
               android:textColor="@android:color/white" />

       </LinearLayout>

       <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:gravity="center"
           android:orientation="horizontal"
           android:background="@android:color/black">

           <TextView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginEnd="20dp"
               android:text="@string/the_counter_second_text"
               android:textSize="32sp"
               android:textColor="@color/white" />

           <TextView
               android:id="@+id/the_second_counter_tv"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="0"
               android:textSize="30sp"
               android:textColor="@android:color/white" />

       </LinearLayout>

       <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:gravity="center"
           android:orientation="horizontal"
           android:background="@android:color/darker_gray">

           <Button
               android:id="@+id/btn_start"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginEnd="20dp"
               android:backgroundTint="@android:color/holo_green_dark"
               android:text="@string/start_text"
               android:textAllCaps="false" />

           <Button
               android:id="@+id/btn_stop"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:backgroundTint="@android:color/holo_red_dark"
               android:text="@string/stop_text"
               android:textAllCaps="false" />


       </LinearLayout>

   </LinearLayout>
</layout>

After completing the UI-related work we go to the MainActivity.kt File:

//MainActivity.kt

package com.example.highlevelcoroutine

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.highlevelcoroutine.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
   //for data binding
   private lateinit var activityMainBinding: ActivityMainBinding
   private lateinit var counterViewModel: CounterViewModel

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
       setContentView(activityMainBinding.root)
       counterViewModel = ViewModelProvider(this)[CounterViewModel::class.java]
       counterViewModel.counter1Toast.observe(this@MainActivity, Observer {
           activityMainBinding.theFirstCounterTv.text = it.toString()
       })
       counterViewModel.counter2Toast.observe(this@MainActivity, Observer {
           activityMainBinding.theSecondCounterTv.text = it.toString()
       })
       counterViewModel.showToast.observe(this@MainActivity, Observer {
           Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
       })
       activityMainBinding.btnStart.setOnClickListener {
           counterViewModel.startCounters()
       }
       activityMainBinding.btnStop.setOnClickListener {
           counterViewModel.stopCounters()
       }
   }
}

In this, we will create one variable for our binding and another variable for our viewmodel.

Before that, we will create our ViewModel and give its name like CounterViewModel.kt:

package com.example.highlevelcoroutine

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch

class CounterViewModel : ViewModel() {

   private fun showToast(message: String) {

       _showToast.value = message
   }

   private var counter1 = MutableLiveData(0)
   private var counter2 = MutableLiveData(0)
   private var _showToast = MutableLiveData("")

   private var counter1Job: Job? = null
   private var counter2Job: Job? = null

   var counter1Toast: LiveData<Int> = counter1
   var counter2Toast: LiveData<Int> = counter2
   var showToast: LiveData<String> = _showToast

   fun startCounters() {
       counter1.value =0
       counter2.value =0
       counter1Job = viewModelScope.launch {
           for (i in 1..5) {
               counter1.value = (counter1.value ?: 0) + 1
               delay(1000)
           }
       }
       counter2Job = viewModelScope.launch {
           for (i in 1..5) {
               counter2.value = (counter2.value ?: 0) + 1
               delay(3000)
           }
       }
       counter1Job?.let { job1 ->
           counter2Job?.let { job2 ->
               val listOfJobs = listOf(job1, job2)
               viewModelScope.launch {
                   listOfJobs.joinAll()
                   showToast("Counters are completed.")
               }
           }
       }
   }
   fun stopCounters() = viewModelScope.launch {
       counter1Job?.cancel()
       counter2Job?.cancel()
   }
}

Here, The main work of the application will be done in this area. Creates two MutableLiveData initialized with the given value. And give it names like counter1 and counter2. Make sure that these variables should be var and private. Create another variable for toast, this variable is the same as counter1 and counter2 but its value is not given like them, It’s a string. 

And after this, for observing these variables in the main activity we are assigning these variables to the livedata variables. Make counter1Toast, counter2Toast and showToast and assign the above 3 MutableLiveData variables here. 

After finishing the variable work, We will create one function called startCounter(). First set the value 0 to both counter variable 1&2. We have 2 types of counters, One will work on 1 sec delay period, and the Second one will work on 3 sec delay period. For this reason, we will create 2 variables of JOB. We will assign it as a null. counter1Job will work on 1 sec delay period and counter2Job will work on 3 sec delay period. We use the for loop to increment the counter to the 5 counts. With 1 and 3 sec delay. We will do this in Scope, viewmodelscope will use it for our processes. 

We will join both jobs cause toast should be shown if both jobs are done or canceled. For this, we use joinAll() function. JoinAll() is work to combine all jobs, If any job is not complete yet then it will not finish the process. So for that reason, we will scope function “let” and create one variable and assign the list of both jobs. And after this in viewmodelscope, we will join the list of both jobs. For showing the toast we will create one small function for toast. Give the name showToast() and put the parameter message and set it as a string. In this function assign the message to the _showToast MutableLiveData. It will show that toast when both jobs will complete. 

To stop the counting process, We will create one function called stopCounters(). In this, we will cancel both jobs. So automatically it will stop increasing the counting. For this reason, we will use the cancel() function. It will help to cancel both jobs.

Here the main work is done. After completing this coding, we will go back to the Main activity to observe and set all live data variables to the text view and buttons.

First, we can use the created variable counterViewModel and get the ViewModel Instance. By ViewModelProvider(). Observe the counter1Toast and set the value to the theFirstCounterTv text view. Same as for counter2Toast, we will assign the value of it to the theSecondCounterTv text view. Observe the showToast variable and set the value to the Toast.

Then go set the onClick of the both start and stop process buttons. startCounters() will be used in the btnStart button and stopCounters() will be used in btnStop button’s OnClick event.

Outputs

First event when completing both processes.

The second event, Stop processes in between execution.

To conclude

We hope that you found this blog useful for learning about coroutines in Android and will be able to use them confidently in your own projects.

Recent Posts

  • Mobile Application

SwiftUI Importants: Best Practices for Developers

Diving deep into SwiftUI This blog post drops us into…

  • Online Booking Engine

Custom Salon Booking System for Salons, Spas & Hospitality

Corporate efficiency and customization are vital in today's fast-paced world,…

  • Mobile Application

Flutter Codemagic CI/CD: Complete Guide to Flutter Automation

Flutter Codemagic CI/CD makes your Flutter app build, test, and…