preface

Do you remember when Google IO officially announced Kotlin as a tier 1 development language for Android? It’s Google IO 2017. Now, two years later, as an Android developer, Kotlin’s ecosystem is getting better and better, with more open source projects and learning materials, and more friends willing to use or try Kotlin. Having been a gold digger for years, I can definitely feel that Kotlin’s hashtag is getting more and more posts (which is still pitifully low). This year’s Google IO also released the Kotlin First slogan, and many new apis and features will come First with Kotlin support. So, to this day, there’s really no reason why Android developers shouldn’t follow Kotlin.

Today I want to talk about Kotlin Coroutine. Although coroutines have been around since Kotlin was released, it wasn’t until The 2018 KotlinConf conference that JetBrain released Kotlin1.3RC that a stable version of the coroutine was made available. Even though the stable version of the coroutine has been around for more than a year, it doesn’t seem to have enough users, at least in my opinion. In the various stages of my coroutine learning, I had few places to turn to for help, leaving the technology group largely unattended. Basically, only some English documents can solve the problem.

I have read a lot of articles about coroutines, to sum up, nothing more than the following categories.

The first category is the translation of popular articles on Medium, which I have translated as well:

Using coroutines on Android (part 1) : Getting The Background

Using coroutines on Android (2) : Getting Started

Using coroutines on Android (3) : Real Work

To be honest, these three articles have really deepened my understanding of coroutines.

The second type is the translation of official documents, I have seen at least five versions of the translation, or feel that it is better to see the official website documents, if the English is really difficult to see, you can compare the translation of Kotlin Chinese station to read.

For a long time after reading the official documentation, I knew almost nothing but GlobalScope. Indeed, the official documentation is pretty much full of sample code written in GlobalScope. So some developers, including myself, write their own code directly to GlobalScope. An accidental opportunity to find that in fact such a problem is very big. In Android, it is generally not recommended to use GlobalScope directly. So how do you use coroutines correctly in Android? Breaking it down a bit, how do you use it directly in an Activity? How does it work with ViewModel, LiveData, LifeCycle, etc.? I’ll illustrate the use of coroutines on Android with simple code examples, and you can follow suit.

Use of coroutines on Android

GlobalScope

In common applications, we want to be able to perform time-consuming tasks asynchronously, such as network requests, data processing, and so on. We also want to be able to cancel ongoing asynchronous tasks when we leave the current page. These two points are exactly what you need to pay attention to when using coroutines. Since it’s not recommended to use GlobalScope directly, let’s first experiment with what it looks like.

private fun launchFromGlobalScope(a) {
    GlobalScope.launch(Dispatchers.Main) {
        val deferred = async(Dispatchers.IO) {
            // network request
            delay(3000)
            "Get it"
        }
        globalScope.text = deferred.await()
        Toast.makeText(applicationContext, "GlobalScope", Toast.LENGTH_SHORT).show()
    }
}
Copy the code

In the launchFromGlobalScope() method, I launch a coroutine directly via GlobalScope.launch(), delay(3000) simulates the network request, and after three seconds, a Toast prompt pops up. There is no problem in use, and Toast can pop up normally. However, when you execute this method and immediately press the return key to return to the previous page, the Toast will still pop up. If the actual development is through the network request to update the page, when the user is no longer on the page, there is no need to request, it will only waste resources. GlobalScope certainly does not fit this profile. The Kotlin documentation actually explains this in detail, as follows:

Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don’t have any job associated with them.

Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged.

Basically, Global Scope is typically used to start top-level coroutines that run throughout the application life cycle and are not cancelled prematurely. Program code should generally use custom coroutine scopes. Direct use of GlobalScope’s Async or Launch methods is strongly discouraged.

The coroutines created by GlobalScope do not have a parent coroutine, and GlobalScope is generally not bound to any lifecycle components. Unless manually managed, it was difficult to meet our actual development requirements. So GlobalScope doesn’t use it when it can.

MainScope

The official documentation mentions the use of custom coroutine scopes. Of course, Kotlin has provided the appropriate coroutine scope MainScope. Take a look at the definition of MainScope:

public fun MainScope(a): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Copy the code

Keep this definition in mind, and we’ll use it later in our use of coroutines for viewModels.

Give our Activity its own coroutine scope:

class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}
Copy the code

The extension launch() function can be used to launch coroutines directly from the main thread, as shown in the following code:

private fun launchFromMainScope(a) {
    launch {
        val deferred = async(Dispatchers.IO) {
            // network request
            delay(3000)
            "Get it"
        }
        mainScope.text = deferred.await()
        Toast.makeText(applicationContext, "MainScope", Toast.LENGTH_SHORT).show()
    }
}
Copy the code

Finally, don’t forget to remove the coroutine from onDestroy() by extending cancel() :

override fun onDestroy(a) {
    super.onDestroy()
    cancel()
}
Copy the code

Now test the launchFromMainScope() method! You’ll find this is exactly what you need. In practice, MainScope could be integrated into BaseActivity without the need to repeat the template code.

ViewModelScope

If you use the MVVM architecture, you won’t write any logical code on the Activity at all, let alone start the coroutine. At this point, most of the work will go to the ViewModel. So how do you define the coroutine scope in the ViewModel? Remember the definition of MainScope() above? Yeah, just bring it in and use it.

class ViewModelOne : ViewModel() {

    private val viewModelJob = SupervisorJob()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

    val mMessage: MutableLiveData<String> = MutableLiveData()

    fun getMessage(message: String) {
        uiScope.launch {
            val deferred = async(Dispatchers.IO) {
                delay(2000)
                "post $message"
            }
            mMessage.value = deferred.await()
        }
    }

    override fun onCleared(a) {
        super.onCleared()
        viewModelJob.cancel()
    }
}
Copy the code

UiScope is the equivalent of MainScope. Calling the getMessage() method has the same effect as launchFromMainScope(). Remember to remove the coroutne in the onCleared() callback of the ViewModel.

You can define a BaseViewModel to handle this logic and avoid repeating the template code. But Kotlin wants you to do the same thing and write less code, so viewModel-ktx comes in. When you look at KTX, you know it’s there to simplify your code. Introduce the following dependencies:

implementation "Androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0 - alpha03"
Copy the code

Then, you don’t need to do anything, just use the coroutine scope viewModelScope. ViewModelScope is an extended property of ViewModel, defined as follows:

val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if(scope ! =null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
        }
Copy the code

If you take a look at the code, it’s a familiar one. When viewModel.oncleared () is called, viewModelScope automatically cancels all coroutines in scope. The following is an example:

fun getMessageByViewModel(a) {
    viewModelScope.launch {
        val deferred = async(Dispatchers.IO) { getMessage("ViewModel Ktx") }
        mMessage.value = deferred.await()
    }
}
Copy the code

At this point, viewModelScope is the best shorthand for what you need. In fact, for the entire article, viewModelScope is still my best choice.

LiveData

Kotlin also gives LiveData the ability to use coroutines directly. Add the following dependencies:

implementation "Androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0 - alpha03"
Copy the code

Call the suspended function that needs to be executed asynchronously directly from the liveData {} code block and call the emit() function to send the processing results. The sample code looks like this:

val mResult: LiveData<String> = liveData {
    val string = getMessage("LiveData Ktx")
    emit(string)
}
Copy the code

You may be wondering that there doesn’t seem to be any display calls, so where does the liveData code block get executed? LiveData {} is automatically executed when LiveData enters the active state. When LiveData enters inactive state, it will be automatically cancelled after a configurable timeout. If it is cancelled before completion, LiveData will run again when it is active again. If the last run ended successfully, it will not be rerun. This means that only auto-cancelled liveData{} can be rerun. Cancellations caused by other causes (such as CancelationException) will not be rerunked.

So the use of LiveData-KTX is limited. For scenarios that require an active refresh by the user, this is not enough. Once a successful execution completes once in a full life cycle, there is no way to trigger again. I don’t know if this sentence is right, I personally understand it this way. Therefore, viewModel-KTX is more applicable and controllable.

LifecycleScope

implementation "Androidx. Lifecycle: lifecycle - runtime - KTX: 2.2.0 - alpha03"
Copy the code

Lifecycle – Run-time – KTX defines the coroutine scope lifecycleScope for each Lifecycle object with extended properties. You can use the lifecycle. CoroutineScope or lifecycleOwner. LifecycleScope for a visit. Example code is as follows:

fun getMessageByLifeCycle(lifecycleOwner: LifecycleOwner) {
    lifecycleOwner.lifecycleScope.launch {
        val deferred = async(Dispatchers.IO) { getMessage("LifeCycle Ktx") }
        mMessage.value = deferred.await()
    }
}
Copy the code

The coroutine scope lifecycleScope is cancelled automatically when LifeCycle calls onDestroy(). We can easily use it in activities/fragments and other life cycle components, but in MVVM there will not be too much logic processing in the View layer, viewModelScope can basically meet the needs of ViewModel. LifecycleScope can also be a bit tasteless. But he has a special use:

suspend fun <T> Lifecycle.whenCreated(a)
suspend fun <T> Lifecycle.whenStarted(a)
suspend fun <T> Lifecycle.whenResumed(a)
suspend fun <T> LifecycleOwner.whenCreated(a)
suspend fun <T> LifecycleOwner.whenStarted(a)
suspend fun <T> LifecycleOwner.whenResumed(a)
Copy the code

You can further lighten the load on the View layer by specifying that suspend functions should be executed at least after a specific lifecycle.

conclusion

The above is a brief introduction to the proper use of coroutines in Android, sample code has been uploaded to Github. For MVVM + coroutine project, you can check out my open source project WanAndroid, and I look forward to your valuable comments.

This article first published wechat official account: BingxinshuaiTM, focusing on Java, Android original knowledge sharing, LeetCode problem solving.

More latest original articles, scan code to pay attention to me!