I created this easy Android app to assist me to know the essential usages of Kotlin coroutines. The app demonstrates learn how to create coroutines jobs and run them concurrently. It most likely will not cowl 100% of the use circumstances, perhaps at the very least 90%?
The app additionally makes use of easy MVVM structure (with out the Mannequin
to be precise).
App Overview
There are 3 buttons and a couple of show texts (left and proper) UI on this app.
- Launch and Async buttons replace the each left textual content and proper textual content UI concurrently
- left textual content and proper textual content are began at -1 as invalid worth.
- When Launch or Async button is clicked, coroutines are created to replace left textual content UI from 0 → 9 and proper textual content UI from 10 → 19
- If the Cancel button is clicked, each texts are stopped being up to date and worth is ready to -1.
- If no Cancel button is clicked, each texts proceed to be up to date till the ultimate worth 9 and 19
There are 2 methods to create coroutines:
CoroutineScope.launch
CoroutineScope.async
CoroutineScope.launch
To create a coroutine, we have to create CoroutineScope
first. In ViewModel
, CorotineScope
is already created (i.e. viewModelScope
). So it’s extremely really helpful to make use of it as an alternative of making it your self. One profit is, all of the CoroutineScope
youngsters shall be routinely cancelled when ViewModel
is destroyed.
currentJob = viewModelScope.launch {
val job1 = launch {
...
}
val job2 = launch {
...
}
job1.be a part of()
job2.be a part of()
...
}
CoroutineScpoe.launch
is a non-blocking perform, and it returns Job
instantly. To realize concurrency, we are able to name CoroutineScpoe.launch
a number of occasions throughout the similar coroutine scope. job1
is accountable to replace the left textual content UI and job2
is accountable to replace the proper textual content UI.
If you wish to look forward to the job to finish, you have to name the Job.be a part of()
droop perform. This may wait till the job is accomplished earlier than it strikes to the subsequent line.
CoroutineScope.async
For creating coroutine that we wish to look forward to it is returned worth, we use CoroutineScope.async
.
Much like CoroutineScpoe.launch
, CoroutineScope.async
is a non-blocking perform. As an alternative of returning Job
, it returns Deferred<T>
. The final line within the async block
, is the return sort T
. For instance, getData()
returns Int
, thus, the T
is Int
sort.
viewModelScope.launch {
val deferred = async {
...
getData()
}
information.worth = deferred.await()
...
}
As an alternative of utilizing Job.be a part of()
, you name Deferred<T>.awailt()
to attend for the CoroutineScope.async
to complete and in addition return the worth from getData()
.
CoroutineScope.withContext()
By default, the coroutines are run on principal/UI thread. You must transfer the long-running duties to completely different thread in order that it does not block the primary/UI thread.
To modify to a distinct thread, you specify CoroutineDispatcher
. Listed here are the widespread pre-defined CoroutineDispatcher
that we are able to use:
Dispatchers.Most important
– principal/UI threadDispatchers.Default
– CPU operation threadDispatchers.IO
– IO or community operation thread
To make use of your personal thread, you’ll be able to create a brand new thread / new CoroutineDispatcher
utilizing newSingleThreadContext("MyOwnThread")
. More often than not, the pre-defined CoroutineDispatcher
are sufficient.
When making a coroutine both with launch
or async
, you’ll be able to specify the CoroutineDispatcher
.
viewModelScope.launch {
launch(Dispatchers.Default) {
loadData()
}
async(Dispatchers.Default) {
loadData()
}
}
Nevertheless, a greater resolution is to make use of CoroutineScope.withContext()
within the droop perform as an alternative of specifying the CoroutineDispatcher
throughout coroutine creation. That is really helpful as a result of it makes the droop perform protected to be referred to as from principal/UI thread.
non-public droop enjoyable loadData() {
withContext(Dispatchers.Default) {
...
}
}
Please notice
CoroutineScope.withContext()
does NOT create a brand new coroutine. It strikes the coroutines to a distinct thread.
Job.cancelAndJoin()
To cancel a coroutine job, we name Job.cancel()
and Job.be a part of()
. More often than not, you’ll be able to simply merely name Job.cancelAndJoin()
. Please notice that Job.cancelAndJoin()
is a droop perform. So you have to name it contained in the coroutine.
enjoyable onCancelButtonClick() {
if (currentJob == null) return
viewModelScope.launch() {
currentJob!!.cancelAndJoin()
}
}
currentJob
is an present coroutine job that was created earlier than.
kotlinx.coroutines.yield()
One essential factor to notice is coroutine cancellation is cooperative. If a coroutine is non-cooperative cancellation, there isn’t any method we are able to cancel it. The coroutine will proceed to runs till it’s full though Job.cancel()
has been referred to as.
To make a coroutine cancellation cooperative, you should utilize:
CoroutineScope.isActive
kotlinx.coroutines.yield()
CoroutineScope.isActive
required CoroutineScope
object to be referred to as, and you have to add logic to exit the coroutine, thus it’s much less versatile. Since yield()
will be referred to as in any droop perform, I personally desire to make use of it.
Please notice the
kotlinx.coroutines.delay()
additionally make the coroutine cancellation cooperative.
For instance, when you have a long-running activity like under, the coroutine won’t honor any Job.cancel()
request.
non-public droop enjoyable simulateLongRunningTask() {
repeat(1_000_000) {
Thread.sleep(100)
}
}
To make it to just accept the Job.cancel()
request, you simply want so as to add yield()
.
non-public droop enjoyable simulateLongRunningTask() {
repeat(1_000_000) {
Thread.sleep(100)
yield()
}
}
kotlinx.coroutines.JobCancellationException
When a coroutine is cancellation is accepted, an kotlinx.coroutines.JobCancellationException
exception shall be thrown. You may catch the exception and carry out some clear up.
currentJob = viewModelScope.launch {
attempt {
val job1 = launch {
...
}
val job2 = launch {
...
}
job1.be a part of()
job2.be a part of()
} catch (e: Exception) {
currentJob = null
}
}
kotlinx.coroutines.coroutineContext
For debugging coroutine, logging is the best method. kotlinx.coroutines.coroutineContext
may be very helpful for logging. It gives the coroutine and thread data.
Please notice that it’s a droop property which may solely be referred to as from the droop perform.
Instance of Utils.log()
utility droop perform to wrap the .Log.d()
:
object Utils {
droop enjoyable log(tag: String, msg: String) {
Log.d(tag, "$coroutineContext: $msg")
}
}
Utils.log("ViewModel", "======= Created launch coroutine - onButtonClick() =======")
Instance of Logcat output:
D/ViewModel: [StandaloneCoroutine{Active}@5261b32, Dispatchers.Main.immediate]: ======= Created launch coroutine - onButtonClick() =======
Some Ideas
To date, all my private tasks don’t use all of the coroutines use circumstances above. I solely use CoroutineScope.launch
and CoroutineScope.withContext()
which is sufficient for me to perform what I need. I do not even have to cancel a coroutine, though I can if I wish to, and the apps nonetheless work completely.
[Update: April 13, 2022]: I did use joinAll()
parallelize just a few community calls as an alternative of working the code in sequential. Instance under:
non-public droop enjoyable fetchArticlesFeed() : Listing<ArticleFeed> = coroutineScope {
val outcomes = mutableListOf<ArticleFeed>()
val jobs = mutableListOf<Job>()
for(url in urls) {
val job = launch {
val xmlString = webService.getXMlString(url)
val articleFeeds = FeedParser().parse(xmlString)
outcomes.addAll(articleFeeds)
}
jobs.add(job)
}
jobs.joinAll()
return@coroutineScope outcomes
}
Supply Code
GitHub Repository: Demo_CoroutinesBasics