Coroutines in Android
Overview
With the help of coroutines we can execute multiple task on a single thread. Coroutines are executed in a thread. One thread can have multiple coroutines. Coroutines run on top of Threads.
Dependency
def coroutines_version = "1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
Basis of Coroutine
- Coroutine Scope - Defines the lifetime of a coroutine
- Coroutine Context - Defines the thread which are used to launch a coroutine
Dispatchers are a way to define threads on which Coroutines are executed. Dispatchers dispatch your Coroutines on a Thread. There are some predefined dispatchers:
- Dispatchers.IO
- Dispatchers.Main
- Dispatchers.Default
There are three scopes
- CoroutineScope - Use this scope in custom class.
- GlobalScope - Use it in an Android application is a bad practice.
- MainScope - It is used generally use in an Activity.
- ViewModelScope - It is used for ViewModels. This scope cancels automatically.
- LifecycleScope - It is used for application components like Activities and Fragments. This scope cancels automatically.
Three are four Coroutine Builders
- Launch - This is used when you do not care about the results (Fire & Forget)
- Async - This is used when you expect result or output from your Coroutine.
- Produce - This is used in channels.
- Actor - This is used in conjuction with channels and pipeline concept.
Use of yield()
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.Main).launch {
task1()
}
CoroutineScope(Dispatchers.Main).launch {
task2()
}
}
private suspend fun task1()
{
Log.d(TAG , "Coroutines :: task1 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task1 : Starting Task 1")
yield()
Log.d(TAG , "Coroutines :: task1 : Ending Task 1")
}
private suspend fun task2()
{
Log.d(TAG , "Coroutines :: task2 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task2 : Starting Task 2")
yield()
Log.d(TAG , "Coroutines :: task2 : Ending Task 2")
}
}
Output
2022-01-07 11:09:56.669 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : main
2022-01-07 11:09:56.669 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : Starting Task 1
2022-01-07 11:09:56.671 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : main
2022-01-07 11:09:56.671 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : Starting Task 2
2022-01-07 11:09:57.255 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : Ending Task 1
2022-01-07 11:09:57.256 5029-5029/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : Ending Task 2
Use of delay()
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.Main).launch {
task1()
}
CoroutineScope(Dispatchers.Main).launch {
task2()
}
}
private suspend fun task1()
{
Log.d(TAG , "Coroutines :: task1 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task1 : Starting Task 1")
delay(1000)
Log.d(TAG , "Coroutines :: task1 : Ending Task 1")
}
private suspend fun task2()
{
Log.d(TAG , "Coroutines :: task2 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task2 : Starting Task 2")
delay(1000)
Log.d(TAG , "Coroutines :: task2 : Ending Task 2")
}
}
Output
2022-01-07 11:17:02.614 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : main
2022-01-07 11:17:02.614 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : Starting Task 1
2022-01-07 11:17:02.625 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : main
2022-01-07 11:17:02.625 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : Starting Task 2
2022-01-07 11:17:03.627 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task1 : Ending Task 1
2022-01-07 11:17:03.635 5275-5275/com.example.coroutines_android D/Coroutines: Coroutines :: task2 : Ending Task 2
Use of join() - Use the join method when you want to execute your code synchronously.
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
printFollowers()
}
}
private suspend fun printFollowers()
{
Log.d(TAG , "Coroutines :: task1 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task1 : Starting Task 1")
delay(1000)
Log.d(TAG , "Coroutines :: task1 : Ending Task 1")
var fbFollowers = 0
val job = CoroutineScope(Dispatchers.IO).launch {
fbFollowers = getFollowers()
}
job.join()
Log.d(TAG , "MainActivity :: printFollowers :: fbFollowers : $fbFollowers")
}
private suspend fun getFollowers() : Int
{
Log.d(TAG , "Coroutines :: task2 : ${Thread.currentThread().name}")
Log.d(TAG , "Coroutines :: task2 : Starting Task 2")
delay(1000)
Log.d(TAG , "Coroutines :: task2 : Ending Task 2")
delay(5000)
return 54
}
}
Output
2022-01-07 18:50:16.799 7642-7687/com.example.coroutines_android D/Coroutines: MainActivity :: printFollowers :: fbFollowers : 54
Use of async() and await()
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
printFollowers()
}
}
private suspend fun printFollowers()
{
val fb = CoroutineScope(Dispatchers.IO).async {
getFollowers()
}
val insta = CoroutineScope(Dispatchers.IO).async {
getInstaFollowers()
}
Log.d(TAG , "MainActivity :: printFollowers :: fbFollowers : ${fb.await()}")
Log.d(TAG , "MainActivity :: printFollowers :: instaFollowers : ${insta.await()}")
}
private suspend fun getFollowers() : Int
{
delay(1000)
return 54
}
private suspend fun getInstaFollowers() : Int
{
delay(1000)
return 113
}
}
Output
2022-01-07 20:29:31.967 9043-9085/com.example.coroutines_android D/Coroutines: MainActivity :: printFollowers :: fbFollowers : 54
2022-01-07 20:29:31.969 9043-9085/com.example.coroutines_android D/Coroutines: MainActivity :: printFollowers :: instaFollowers : 113
Use of async and await with launch()
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
printFollowers()
}
}
private suspend fun printFollowers()
{
CoroutineScope(Dispatchers.IO).launch {
val fb = async { getFollowers() }
val insta = async { getInstaFollowers() }
Log.d(TAG , "MainActivity :: printFollowers :: fbFollowers : ${fb.await()}")
Log.d(TAG , "MainActivity :: printFollowers :: instaFollowers : ${insta.await()}")
}
}
private suspend fun getFollowers() : Int
{
delay(1000)
return 54
}
private suspend fun getInstaFollowers() : Int
{
delay(1000)
return 113
}
}
Output
2022-01-08 00:31:09.355 9362-9408/com.example.coroutines_android D/Coroutines: MainActivity :: printFollowers :: fbFollowers : 54
2022-01-08 00:31:09.355 9362-9408/com.example.coroutines_android D/Coroutines: MainActivity :: printFollowers :: instaFollowers : 113
Parent-Child Coroutine Version 1
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
execute()
}
}
private suspend fun execute()
{
val parentJob = GlobalScope.launch(Dispatchers.Main) {
Log.d(TAG , "MainActivity :: execute :: parentJob :: ${this.coroutineContext}")
val childJob = launch {
Log.d(TAG , "MainActivity :: execute :: childJob :: ${this.coroutineContext}")
}
}
}
}
Output
2022-01-08 00:53:46.068 9614-9614/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: parentJob :: [StandaloneCoroutine{Active}@ff2c9f2, Dispatchers.Main]
2022-01-08 00:53:46.071 9614-9614/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: childJob :: [StandaloneCoroutine{Active}@713e843, Dispatchers.Main]
Parent-Child Coroutine Version 2
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
execute()
}
}
private suspend fun execute()
{
val parentJob = GlobalScope.launch(Dispatchers.Main) {
Log.d(TAG , "MainActivity :: execute :: parentJob :: ${this.coroutineContext}")
val childJob = launch(Dispatchers.IO) {
Log.d(TAG , "MainActivity :: execute :: childJob :: ${this.coroutineContext}")
}
}
}
}
Output
2022-01-08 01:17:20.915 9859-9859/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: parentJob :: [StandaloneCoroutine{Active}@ff2c9f2, Dispatchers.Main]
2022-01-08 01:17:20.917 9859-9903/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: childJob :: [StandaloneCoroutine{Active}@713e843, Dispatchers.IO]
Parent-Child Version 3
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
execute()
}
}
private suspend fun execute()
{
val parentJob = GlobalScope.launch(Dispatchers.Main) {
Log.d(TAG , "MainActivity :: execute :: Parent Started")
val childJob = launch(Dispatchers.IO) {
Log.d(TAG , "MainActivity :: execute :: Child Started")
delay(5000)
Log.d(TAG , "MainActivity :: execute :: Child Ended")
}
delay(3000)
Log.d(TAG , "MainActivity :: execute :: Parent Ended")
}
parentJob.join()
Log.d(TAG , "MainActivity :: execute :: Parent Completed")
}
}
Output
2022-01-08 01:34:04.679 10318-10318/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Started
2022-01-08 01:34:04.684 10318-10365/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Child Started
2022-01-08 01:34:07.689 10318-10318/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Ended
2022-01-08 01:34:09.690 10318-10365/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Child Ended
2022-01-08 01:34:09.692 10318-10365/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Completed
Parent Job Cancel
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
execute()
}
}
private suspend fun execute()
{
val parentJob = GlobalScope.launch(Dispatchers.Main) {
Log.d(TAG , "MainActivity :: execute :: Parent Started")
val childJob = launch(Dispatchers.IO) {
Log.d(TAG , "MainActivity :: execute :: Child Started")
delay(5000)
Log.d(TAG , "MainActivity :: execute :: Child Ended")
}
delay(3000)
Log.d(TAG , "MainActivity :: execute :: Parent Ended")
}
delay(1000)
parentJob.cancel()
parentJob.join()
Log.d(TAG , "MainActivity :: execute :: Parent Completed")
}
}
Output
2022-01-08 01:45:17.563 10520-10520/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Started
2022-01-08 01:45:17.564 10520-10565/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Child Started
2022-01-08 01:45:18.659 10520-10563/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Completed
Child Job Cancel
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
execute()
}
}
private suspend fun execute()
{
val parentJob = GlobalScope.launch(Dispatchers.Main) {
Log.d(TAG , "MainActivity :: execute :: Parent Started")
val childJob = launch(Dispatchers.IO) {
Log.d(TAG , "MainActivity :: execute :: Child Started")
delay(5000)
Log.d(TAG , "MainActivity :: execute :: Child Ended")
}
delay(3000)
childJob.cancel()
Log.d(TAG , "MainActivity :: execute :: Parent Ended")
}
parentJob.join()
Log.d(TAG , "MainActivity :: execute :: Parent Completed")
}
}
Output
2022-01-08 01:53:49.365 10816-10816/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Started
2022-01-08 01:53:49.374 10816-10902/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Child Started
2022-01-08 01:53:52.383 10816-10816/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Ended
2022-01-08 01:53:52.386 10816-10902/com.example.coroutines_android D/Coroutines: MainActivity :: execute :: Parent Completed
withContext - It behaves like a suspend function. Means it will block the function until it is executed.
class MainActivity : AppCompatActivity()
{
companion object
{
const val TAG = "Coroutines"
}
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
executeTask()
}
}
private suspend fun executeTask()
{
Log.d(TAG , "MainActivity :: executeTask :: Before")
withContext(Dispatchers.IO) {
delay(1000)
Log.d(TAG , "MainActivity :: executeTask :: Inside")
}
Log.d(TAG , "MainActivity :: executeTask :: After")
}
}
Output
2022-01-08 03:03:59.139 11757-11800/com.example.coroutines_android D/Coroutines: MainActivity :: executeTask :: Before
2022-01-08 03:04:00.173 11757-11800/com.example.coroutines_android D/Coroutines: MainActivity :: executeTask :: Inside
2022-01-08 03:04:00.175 11757-11800/com.example.coroutines_android D/Coroutines: MainActivity :: executeTask :: After
Do's and Dont's
- Do not use GlobalScope or runblocking in your Android Application. They both will make your code synchronous rather than asynchronous.
- If you are using Activity then use can use lifeCycleScope to launch/async/produce/actor, a corutine directly in a function.
- If you are using Fragment then use viewLifeCycleOwner to launch/async/produce/actor, a coroutine with the help of lifeCycleScope in a function.
viewLifecycleOwner.lifecycleScope.launch {
withContext(Dispatchers.IO) {
getAEPSResponse()
}
}
- If you are using ViewModel then you can directly use viewModelScope to launch/ async/produce/actor, a coroutine directly in a function.
- If you get the error "Only the original thread that created a view hierarchy can touch its views." try the following code
activity?.runOnUiThread(Runnable {
edtName.setText(this.name)
edtDOB.setText(this.dob)
edtAddress.setText(this.address)
edtPinCode.setText(this.pin)
})
- Do not forget to specify the dispatcher of a child coroutine. If you do not specify it then it will take the dispatcher of parent coroutine.
- When you are using withContext, you have to specify a dispatcher.
Comments
Post a Comment