Android Kotlin Coroutine Scopes

Lou Morda
5 min readMay 1, 2021

--

This article describes commonly used Kotlin coroutine scopes typically used in an Android project, specifically with an MVVM architecture. The following scopes will be discussed:

  • GlobalScope
  • CoroutineScope(Dispatchers.IO)
  • lifecycleScope
  • viewModelScope + LiveData

The article should showcase that the ideal solution is to use a combination of Jetpack viewModelScope + LiveData. This gives us the Android built-in support for coroutine cancellation when the ViewModel onCleared function is called which prevents leaking the coroutine. And combined with LiveData we have a perfect way to observe and display the result of the coroutine function in the Activity/Fragment on the main thread in a lifecycle friendly way.

GlobalScope

I think a common misconception about GlobalScope is that it blocks the main thread. When I was first learning about coroutines, I assumed GlobalScope was the same as running a time consuming operation on the main thread. Most articles I read said something along the lines of GlobalScope bad and when I think threads and bad in the world of Android my mind instinctively goes to blocking the main thread!

Let’s use this simple long disk operation in a MockDiskService as an example

The biggest problem with GlobalScope is that the coroutine can very easily be leaked. So when you think GlobalScope, don’t think blocking main thread bad, think memory leak bad. Let’s try to run this long disk operation from MainActivity:

Roman Elizarov gives a detailed explanation of how GlobalScope can cause memory leaks in The Reason to avoid GlobalScope:

And as you can see we don’t have a very good way of displaying something on the main thread once the operation is complete. Showing the toast message will crash the app.

CoroutineScope(Dispatchers.IO)

Since I thought GlobalScope was blocking the main thread I decided to create my own CoroutineScope and force it to dispatch onto an IO thread. I was actually trying to solve a problem that didn’t exist: the fact that I thought GlobalScope was blocking the main thread. What I didn’t realize is that this scope has the same problem as GlobalScope. Since this scope I’ve created does not contain a Job, a default Job() is created and I’m still not monitoring/cancelling this coroutine if needed. Again this can lead to a memory leak when we try to run this operation in MainActivity:

I solved a problem that didn’t exist, and I still have the potential memory leak!

lifecycleScope

Android Jetpack introduced a scope that is automatically cancelled for us. And it dispatches onto the main thread so that we can display the result. Let’s solve the memory leak problem (warning: this blocks the main thread!)

Yay, we’ve solved the potential memory leak! Google has rescued us with their lifecycleScope. We don’t even have to call a cancel function this is all handled for us automatically if the activity is destroyed. Here’s a great article by Florina explaining how that works:

BUT Since we’re calling a function that is not suspending using a scope that dispatches onto the main thread by default, the main thread is blocked. In fact all of the Jetpack CoroutineScopes are main thread blocking. That is why all coroutines functions launched from an Activity or Fragment directly should be suspending. Here is a tutorial showing the importance of this fundamental:

https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/04_Suspend

“when we use a suspend function to perform a request, the underlying thread isn't blocked.”

Lets make our mock disk service read the data file in a suspending function. We will use the withContext coroutine builder and dispatch the coroutine onto an IO thread. The withContext builder is basically a combination of async/await and so this suspending function will not return until the operation is complete

And now let’s call this new suspending function from the activity:

We now have a method to run this very long running operation from MainActivity that does not block the main thread and is cancelled in the event MainActivity is destroyed. No more memory leaks! And since lifecycleScope dispatches on the main thread we can show a toast letting the user know the operation is complete.

This was a major milestone for me when learning coroutines:

  1. GlobalScope does not block main thread, but causes potential memory leaks and cannot display results on the main thread
  2. Jetpack scopes like lifecycleScope block the main thread, so we must use suspending functions

It was counter intuitive. We’ve always been taught block main thread bad. So my brain associated GlobalScope with good, and Jetpack scopes with bad. But when it comes to coroutines cancellation is just as important as blocking the main thread. This is why Jetpack coroutine scopes are so great. Just make sure you use them with suspending functions.

viewModelScope + LiveData

We are now coroutine experts. We know how to run long running operations that do not block the main thread and do not cause memory leaks. But as we know it’s best to separate our View layer from our Service layers. At the very least we should add a ViewModel in between MainActivity and MockDiskService (a repository layer would also be great, and maybe even a domain layer or use case layer). Since most of us are already architecture gurus, we’ve already read many articles like this:

I won’t go into detail about the importance of MVVM. Since we’re all Android architecture experts who just need to learn more about coroutines, we’ve already figured out we need a way to add a ViewModel layer and still run our long running disk operation. Jetpack viewModelScope is the solution for us. From MainActivity we just make one call to a ViewModel function:

We’re just calling a ViewModel function and simply observing when the operation is complete in the MainActivity. All the magic happens in the ViewModel:

In order to notify the ViewModel the disk operation is complete from the Service layer, we’ll just return the Boolean result value:

We use the Jetpack viewModelScope to launch the coroutine which automatically uses a Job tied to the lifecycle of the ViewModel. Now when the ViewModel onCleared function is called, Jetpack automatically cancels the coroutine and we avoid any memory leaks! With the help of LiveData we’re able to notify the MainActivity that the operation is complete so that it can display this info to the user.

This approach solves all of our problems:

  1. Does not block main thread
  2. Is not a memory leak
  3. Displays data in a lifecycle aware way
  4. Uses a ViewModel layer to separate the View and Service layers

Congratulations, you are now using ViewModel + LiveData in your app!!

Level Up

The conclusion of this article is that ViewModel + LiveData is your friend. However if you’d like to take your skills to the next level, I’d recommend checking out the liveData + emit approach:

Github Sample

https://github.com/lmorda/android-coroutine-scopes

--

--

Lou Morda
Lou Morda

Responses (1)