How Coroutines Shape New Ways to Develop

Now is a good time to boost your knowledge of coroutines and their use in Android development.

César Gómez
Yellowme
Published in
13 min readSep 8, 2020

--

During the past month, I’ve experimented with Kotlin coroutines in Android, trying to cover every angle that a full-fledged project would have. I could describe coroutines with the following statements:

  1. They are a clean and elegant solution for background tasks.
  2. They are integrated across many of the Android framework’s libraries.
  3. They work well in isolation, but call for additional integration to create a complete solution.

From the most basic stuff like network calls to testing code that has coroutines, I had a fair amount of information from various sources collected. This article is a compilation of that information.

What are coroutines?

This article assumes that you already have at least basic understanding of coroutines, including starting tasks on different threads using dispatchers.

Let’s take a look at a (somewhat sparse) definition from the Kotlin documentation:

Essentially, coroutines are light-weight threads.

I understand the purpose of this definition, since it makes sense to differentiate coroutines from threads: threading is expensive and coroutines are not. So you wouldn’t want to apply the mindset of using them sparingly like you probably do with threads; on the contrary, you should think of using them as you need them without worrying about “launching too many coroutines” (of course, you wouldn’t want to launch coroutines when they aren’t necessary).

However, when I think about the use of coroutines, a more appropriate definition comes to mind:

Coroutines are a smart threading solution, optimized for common developer tasks.

This is very easy to see when you learn that coroutines make use of dispatchers, which determine the thread or threads that the coroutine will run on. Coroutines themselves aren’t threads but are able to run on different threads with very simple builders that can be configured with those dispatchers. Let’s look at an example for more clarity.

A simple example

Let’s see an example that clarifies that launching a coroutine isn’t the same as starting a thread. We’ll do this by materializing the great example that Roman Elizarov made from his article on the difference between blocking a thread and suspending a coroutine.

Let’s have a computationally intensive operation run on the main thread in an Android app. We want to press a button to start calculating a large probable prime number, show a loading progress bar while it is calculating, and display the result once done. We’ll use a ViewModel to hold the data values.

Let’s observe the number value in our view (could be an Activity or Fragment). Here, Resource is just a class that manages the state of the information retrieval:

When the information is being computed, we should see a loading ProgressBar by our logic. However, look how the app’s UI freezes when we press the button to call viewModel.findBigPrime() to start computing the value:

Ok, so how do we fix this? We can try making our ViewModel’s method a suspend function:

Notice how the suspend modifier is greyed out, indicating that the modifier is redundant. We’ll get to that shortly. We’ll also need to run this function inside the scope of a coroutine. We can use the lifecycleScope of our view to launch this function:

Now let’s see what happens to the UI:

The UI still freezes. That’s because calling a suspend function:

… doesn’t make the code inside it non-blocking. So, let’s make our function launch a new coroutine that uses the Default dispatcher.

Now let’s see if it solved the frozen UI problem:

Ok, now we finally stopped blocking the main thread and our app shows a loading indicator while the number is being computed. Let’s inspect our suspend function a little bit to see what’s going on. Is the function itself running on the main thread or on a different thread?

Even though it is launched from a coroutine, findBigPrime() is running on the main thread. However, the function itself launches a new coroutine, configures it to run using Dispatchers.Default and suspends its execution until the block invoked finishes. That specific block is run in the background, thanks to the Default dispatcher, which is backed by a thread pool on JVM. You can see that the line where the coroutine is launched is marked with a green icon. This indicates a suspend function call. The suspend modifier allows a function to launch other suspending functions. So now you see that the modifier isn’t greyed out since it isn’t redundant anymore.

If we have functions that may block their caller thread, we should make use of dispatchers to make the code run in the background:

To make everybody’s life simpler we use the following convention: suspending functions do not block the caller thread. — Roman Elizarov in Blocking threads, suspending coroutines

Architecture Components Support

Activities or Fragments, ViewModels, and LiveData instances have their own coroutine scopes to launch and execute background tasks without worrying about these processes outliving the lifecycle of the component.

Lifecycle

If you want to launch coroutines that are scoped to the lifecycle of Activities or Fragments, you can use the lifecycle KTX extension. This provides your Activity or Fragment with its own coroutine scope. Any coroutine that is running when the view’s lifecycle is destroyed gets cancelled.

ViewModel

Personally, I think this one’s going to be the one you’ll use the most, since you will usually make repository calls from a ViewModel, and the start and end of these operations shouldn’t be scoped to a lifecycle. ViewModel’s capabilities help us with that.

This goes to show how useful coroutines’ structured concurrency can be to make some operations inherently cancellable. In most cases, no further logic is needed; no trailing operations means no trailing references to destroyed components, so no memory leaks. Note: if you use the ViewModel’s KTX extension library, you don’t have to import Lifecycle’s library separately.

Room

Room has support for using coroutines to run database transactions. In this particular case, since Room generates all the transaction code from a DAO, all you need to do is add the suspend modifier to your functions:

You can see more about it in the Android Developersarticle on Room’s Coroutines support.

As of now, we’ve seen some good examples of how coroutines simplify code, and also how the Android framework takes advantage of that simplicity to bring first-class support to many important components, like lifecycle owners, ViewModels, and Room databases. Let’s dive into an aspect of coroutines that can be both positive and negative: fully integrating them within your project requires some additional work, since it represents a totally different approach to dealing with asynchrony.

Example: A simple Pokédex app

There’s a famous Pokémon API we can use to make a simple, two-screen Android app. The first screen is a list of Pokémon. Clicking on a Pokémon of said list takes us to a screen showing the Pokémon’s details.

Retrofit support for coroutines

Since version 2.6.0 of the Retrofit library, you can add the suspend modifier to your API’s interface functions. So you go from this:

… to this:

Besides the modifier, you also can return the expected response object directly, instead of a Call. That’s because you won’t have to call the enqueue method anymore. You can expect Retrofit to return PokemonDetails directly. So your repository call gets simplified:

Of course, error handling still has to be done. For example, we use the Resource class to manage the state of our LiveData, so the actual repository function is more complex than this. It is beyond the scope of this document, though, so this is just to show what the callback object you created gets reduced to once you switch to coroutines with Retrofit.

Notice how this function has to be a suspend call, since it calls another suspend function, which is your API service function. This also means our ViewModel will have to call the repository function from the context of a coroutine. Which is good, since now we’ll be able to configure our ViewModel call with the IO Dispatcher, optimized for IO tasks:

Cold streams with Flow

For our Pokémon list, we can take advantage of Paging 3 to expose the paged list response from the PokéAPI as a Flow to our adapter. This way, we can represent the data of our paged list as a cold stream of values, that is, a retrieval of data that runs only while the data stream is being collected. We return the flow from a repository function:

In our ViewModel, we simply prepare and expose the value that we need:

Finally, in our view, we collect the data and submit it to the adapter:

Naturally, there’s a bunch of additional adapter configuration to do, and further customization of the paged list that is available. The focus of this article is only the fact that you can expose the data from the list using Flow. You can dive into Paging 3 from the Android documentation to learn more about the Paging library, and also check their video giving great insight into the library’s capabilities.

Testing

Finally, let’s dive into testing code that contains coroutines. We’ll unit-test the ViewModel for the detailed view of a Pokémon. We can mock our repository’s answer, since we only want to verify that the details update when our ViewModel’s Pokémon ID value is set. You can use the MockK library to stub the response of your repository method, which is a suspend function. At the end of the repository’s response, we want to check that the Resource.Status is SUCCESS. Since it is a LiveData value, we need to observe it for the liveData coroutine block to execute, so we use the observeForTesting extension function.

Right off the bat, the test will fail because our ViewModel’s emit() functions will try to combine their context withDispatchers.Main. The Main dispatcher runs on the main thread of your application and depends on Android’s getMainLooper() method, which means we can’t use it for unit tests. What can we do about this? Well, taking a look at what we do with LiveData should give us a pretty good idea of the correct approach. You see, LiveData requires a special rule inside your unit tests for the same reason: it sets values from the main thread, so it uses Android’s main Looper. So what we do is swap LiveData’s executor with one we can run in our local machine using InstantTaskExecutorRule.

We can create similar behavior for running coroutines inside unit tests, but we’ll need access to a dispatcher. Fortunately, there’s a testing library for coroutines we can use. The kotlinx-coroutines-test library provides some special functionality designed for tests (keep in mind that this library is still experimental):

  • runBlockingTest method: similar to runBlocking in that it blocks its caller thread until completion. But it also skips past any delay call in your code to prevent your tests from taking extra time.
  • TestCoroutineDispatcher: a dispatcher we can use to replace the Main dispatcher during tests.

For this example, we’d only be implementing the dispatcher for a single test class. However, in a real project, you would have to put this boilerplate code inside a lot of classes, so it’s best to put it all inside a JUnit rule:

  • Dispatchers.setMain is the method by which we can assign a custom dispatcher that will be used when we call Dispatchers.Main.
  • We assign our TestCoroutineDispatcher as the Main dispatcher at the start of every test.
  • At the end of every test, we reset the Main dispatcher and run cleanupTestCoroutines() to make sure no other work is running when the test finishes.

Let’s add this rule to our test class and run our test inside a runBlockingTest block from the rule’s test dispatcher:

Let’s take a look at the test result:

From the look of it, we got a race condition: our Resource.Status value from the LiveData should be SUCCESS, but it isn’t even set yet, so we get an NPE. This means that we reach our test’s assertion before the value gets properly updated. Why is this, if we ran our test inside runBlockingTest?

Upon inspection of our ViewModel, we find the problem: even though we’re launching our test code and any of its coroutines from the TestCoroutineDispatcher, our ViewModel is using a LiveData coroutine builder that uses the IO dispatcher to emit its values. This makes that coroutine run on a different thread, causing our race condition:

This actually works for the application, but since it’s hardcoded in the ViewModel, we can’t use a different dispatcher in our tests. For testability, we should make the dispatcher a dependency of our class, so we can easily swap whatever we’d use in our application with the test dispatcher in our tests. If you’ve used Hilt, then you probably noticed the @ViewModelInject constructor annotation in our ViewModel’s header. Automating the dispatcher’s injection should make things easier and faster as we add more ViewModels in our application, and also saves us the trouble of manually configuring every single ViewModel with a dispatcher. Let’s create a simple provider of CoroutineDispatcher instances that returns Dispatchers.IO:

Of course, this is a simplified example. You may require a dispatcher other than the IO dispatcher for specific ViewModels. You could take a look at the Hilt documentation, specifically at qualifiers, if you want more info on how to return different instances of the same type using simple annotation classes.

Now, let’s inject the dispatcher in our ViewModel:

Finally, let’s create a ViewModel instance passing the TestCoroutineDispatcher in the test class and run the test:

Now the test passes; we’ve made sure that the coroutine block that emits the LiveData values always runs before our assertion, since that coroutine block now runs using our test dispatcher.

If you want to know more about testing coroutines, you can take a look at the Testing Coroutines on Android talk from the Android Dev Summit 2019:

Summary

This post has been long enough, so let’s go over what we’ve learned. We’ve learned that coroutines simplify the creation of asynchronous code with suspension and dispatcher task delegation. We’ve also seen that Jetpack libraries are already taking advantage of coroutines to simplify and safely run many operations, like database transactions, triggering code blocks when LiveData instances become active, and scoping tasks to a specific component, like ViewModels.

Finally, we learned a lot of additional considerations for coroutines that I’d like to summarize here:

  • Simplify Retrofit network calls using suspend functions. This will also require changes to your repository code.
  • Mind the use of dispatchers when running tasks in the background. Always make sure you’re using the right dispatcher for the job and switch from the Main dispatcher if your code isn’t main-safe.
  • You may want to automate the injection of those dispatchers, especially if you’re working on a big project. Passing the dispatcher manually for every class that uses it may result in a lot of boilerplate code.
  • Make sure you’re scoping your operations appropriately. Make sure no operation keeps running after its caller gets destroyed. Again, you may want to take advantage of the already available scopes that the KTX libraries provide.
  • When testing, avoid race conditions by using TestCoroutineDispatcher to run every coroutine that would usually run on different threads.

Final Thoughts and Conclusion

Aren’t coroutines young?

This is true. Coroutines are quite new, so you may be skeptical about using them in your projects. Furthermore, a tried-and-true solution already exists: RxJava. They are both solutions for asynchrony, but RxJava has been in development for a few years now, while the first stable version of coroutines will turn 2 in October, 2020; Flow is just over a year old. Because of this, some code that we write using coroutines may not be production-ready yet: Paging 3 is still in alpha and the testing library for coroutines is still in experimental phase.

However, this doesn’t mean that stability isn’t approaching. There are some Jetpack libraries that support coroutines and are stable as of the writing of this article, like the KTX extensions and Room. Other libraries like Retrofit for networking and MockK for mocking in tests also have support for coroutines. And for libraries that don’t have a stable release yet, they do have comprehensive documentation for you to start learning so you’re good to go once these releases come.

Why is this important?

In 2020, Android development will continue to be Kotlin-first. We’ve been listening to your feedback, and will continue partnering with JetBrains to improve your experience with Kotlin. — Android’s commitment to Kotlin

Coroutines are an important part for the present and future of Android development. They aren’t just a feature of the Kotlin library. They are designed to be a new fundamental part of Kotlin, and by extension, of Android. So the libraries that are providing first-class support for coroutines are an indication that moving forward, this will be the standard way of dealing with asynchronous programming in your Android apps. That’s why, even though it’s an early stage, learning to use them and starting to integrate them in Android apps is a smart move for any developer, in my opinion.

Sources

--

--

César Gómez
Yellowme

Android Developer, Software Engineer at Yellowme