Coroutines On Android (part III): Real work

Sean McQuillan
May 21 · 12 min read

Other articles in this series:

Solving real-world problems with coroutines

Part one and two of this series focused on how coroutines can be used to simplify code, provide main-safety on Android, and avoid leaking work. With that background, they look like a great solution to both background processing and a way to simplify callback based code on Android.

  1. Streaming requests are requests that continue to observe changes and report them to caller — they don’t complete when the first result is ready.

One shot requests

A one shot request is performed once each time it’s called and completes as soon as a result is ready. This pattern is the same as a regular function call — it gets called, does some work, then returns. Due to the similarity to function calls they tend to be easier to understand than streaming requests.

A one shot request is performed each time it’s called. It stops executing as soon as a result is ready.

For an example of a one shot request, consider how your browser loaded this page. When you clicked the link to this post your browser sent a network request to the server to load the page. Once the page was transferred to your browser it stopped talking to the backend — it had all the data it needed. If the server modified the post, the new changes would not be shown in your browser — you would have to refresh the page.

Problem: Displaying a sorted a list

Let’s explore one-shot requests by looking at how you might display a sorted list. To make the example concrete, let’s build an inventory app for use by an employee at a store. It will be used to lookup products based on when they were last stocked — they’ll want to be able to sort the list both ascending and descending. It has so many products that sorting it may take almost a second — so we’ll use coroutines to avoid blocking the main thread!

As a general pattern, start coroutines in the ViewModel.

The ViewModel uses a ProductsRepository to actually fetch the data. Here’s what that looks like:

A repository should prefer to expose regular suspend functions that are main-safe.

Note: Some background save operations may want to continue after user leaves a screen — and it makes sense to have those saves run without a lifecycle. In most other cases the viewModelScope is a reasonable choice.

Suspend functions in Room are main-safe and run on a custom dispatcher.

The one shot request pattern

That’s the complete pattern for making a one shot request using coroutines in Android Architecture Components. We added coroutines to the ViewModel, Repository, and Room and each layer has a different responsibility.

  1. Repository exposes regular suspend functions and ensures they are main-safe.
  2. The database and network expose regular suspend functions and ensures they are main-safe.

Our first bug report!

After testing that solution, you launch it to production and everything is going well for weeks until you get a really strange bug report:

  1. Run the sort in the Room dispatcher.
  2. Show the result of the sort.

When starting a new coroutine in response to a UI event, consider what happens if the user starts another before this one completes.

This is a concurrency bug and it doesn’t really have anything to do with coroutines. We’d have the same bug if we used callbacks, Rx, or even an ExecutorService the same way.

The best solution: Disable the button

The fundamental problem is that we’re doing two sorts. We can fix that by making it only do one sort! The easiest way to do that is to disable the sort buttons to stop the new events.

Disabling the buttons while a sort runs using _sortButtonsEnabled in sortPricesBy.

Concurrency patterns

The next few sections explore advanced topics — and if you’re just starting with coroutines you don’t need to understand them right away. Simply disabling the button is the best solution to most problems you’ll run across.

  1. Queue the next work and wait for the previous requests to complete before starting another one.
  2. Join previous work if there’s already a request running just return that one instead of starting another request.

Solution #1: Cancel the previous work

In the case of sorting, getting a new event from the user often means you can cancel the last sort. After all, what’s the point of continuing if the user has already told you they don’t want the result?

Using cancelPreviousThenRun to ensure that only one sort runs at a time.

Consider building abstractions to avoid mixing ad-hoc concurrency patterns with application code.

Important: This pattern is not well suited for use in global singletons, since unrelated callers shouldn’t cancel each other.

Solution #2: Queue the next work

There’s one solution to concurrency bugs that always works.

A Mutex lets you ensure only one coroutine runs at a time — and they will finish in the order they started.

Solution 3: Join previous work

The third solution to consider is joining the previous work. It’s a good idea if the new request would re-start the exact same work that has already been half completed.

Join previous work is a great solution to avoiding repeated network requests.

What’s next?

In this post we explored how to implement a one shot request using Kotlin coroutines. To start out we implemented a complete pattern showing how to start a coroutine in the ViewModel and then expose regular suspend functions from a Repository and Room Dao.

Android Developers

The official Android Developers publication on Medium

Sean McQuillan

Written by

Android Developer Advocate @Google

Android Developers

The official Android Developers publication on Medium