HomeAndroidKotlin Coroutines Fundamentals - Easy Android App Demo

Kotlin Coroutines Fundamentals – Easy Android App Demo


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

Kotlin_Coroutines_Basics_Simple_Android_App_Demo_01.gif

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 09 and proper textual content UI from 1019
  • 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 thread
  • Dispatchers.Default – CPU operation thread
  • Dispatchers.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

See Additionally

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments