Use Kotlin Coroutines in your Android App

1. Before you start

In this Codelab, you’ll learn how to use Kotlin coroutines in Android applications — the recommended way to manage background threads, simplifying code by reducing the need for callbacks. Coroutines are a feature of Kotlin that converts asynchronous callbacks to sequential execution of long-running tasks, such as database or network access.

This is a code snippet that gives you an idea of what to do.

// Async callbacks
networkRequest { result ->
   // Successful network request
   databaseSave(result) { rows ->
     // Result saved}}Copy the code

Callback-based code is converted into sequential execution code using coroutines.

// The same code with coroutines
val result = networkRequest()
// Successful network request
databaseSave(result)
// Result saved
Copy the code

You’ll start with an existing application built with architectural components that uses the callback style for long-running tasks.

By the end of this Codelab, you will be experienced enough in your application to use coroutines to load data from the network, and you will be able to integrate coroutines into your application. You will also become familiar with best practices for coroutines and how to write tests against code that uses coroutines.

A prerequisite for

  • Familiarize yourself with architectural componentsViewModel,LiveData,RepositoryRoom.
  • Familiar with Kotlin syntax, including extension functions and lambda.
  • Basic understanding of using threads on Android, including main thread, background thread, callback.

What can you do

  • Call code written in coroutines and get results.
  • Use suspend functions to order asynchronous code.
  • uselaunchrunBlockingTo control how the code is executed.
  • Learn to usesuspendCoroutineTechniques for converting existing apis into coroutines.
  • Use coroutines with architectural components.
  • Learn best practices for testing coroutines.
  • The relevantRoom, please refer toUse the Room DAO to access data.
  • Refer to the Application Architecture Guide for an introduction to the additional architectural components used in this Codelab.
  • See Kotlin Bootcamp for Programmers for an introduction to Kotlin syntax.
  • For an introduction to the basics of threading on Android, see Running Android Tasks in Background Threads.

What do you need

Android Studio 4.1 (Codelab may work with other versions, but some things may be missing or look different).

2. Start setting

Download the code

Click the following link to download all the code for this Codelab:

Download Zip

. Or clone the GitHub repository from the command line using the following command:

$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git
Copy the code

The Kotlin-Coroutines repository contains two Codelab code. This Codelab uses items in the Coroutines – Codelab directory. There are two application modules in this project:

  • Start – A simple application that uses Android architectural components to which you will add coroutines
  • Finished_code – Project to which coroutines have been added

3. Run and start the sample application

First, let’s take a look at what the initial sample application looks like. Follow these instructions to open the sample application in Android Studio.

  1. If you downloadkotlin-coroutinesZip file, please decompress the file.
  2. Open it in Android Studiocoroutines-codelabThe project.
  3. Select the start application module.
  4. Click on theRun button, and then select an emulator or connect to your Android device, which must be able to Run Android Lollipop (minimum SDK supported is 21).Kotlin CoroutinesThe screen should appear:

If you see the “Android Framework is detected. Click to configure” error message, make sure you open the Coroutines-Codelab directory and not the parent directory.

This starter application uses threads to increment the count with a short delay after you press the screen. It also grabs the new title from the network and displays it on the screen. Try it now, and you should see the count and message change after a short delay. In this Codelab, you will transform the application to use coroutines.

This application uses architectural components to separate the UI code in MainActivity from the application logic in MainViewModel. Take the time to familiarize yourself with the structure of the project.

  1. MainActivityDisplay UI, register click listener, can display oneSnackbar. It passes events toMainViewModelAnd according to theMainViewModelIn theLiveDataUpdate the screen.
  2. MainViewModelTo deal withonMainViewClickedAnd will be usedLiveDataMainActivityCommunication.
  3. ExecutorsDefines theBACKGROUNDIt can run things on background threads.
  4. TitleRepositoryGet the results from the network and save them to the database.

Add coroutines to projects

To use coroutines in Kotlin, you must include the Coroutines-core library in your project’s build.gradle (module: app) file. The Codelab project has done this for you, so you can complete Codelab without doing this.

Coroutines on Android are available as core libraries, as well as Android-specific extensions:

  • kotlinx-coroutines-core– Use the main interface of coroutines in Kotlin
  • kotlinx-coroutines-android– Supports the Android main thread in coroutines

The Starter app already includes dependencies in build.gradle. To create a new app project, open build.gradle (module: app) and add coroutine dependencies to the project

dependencies {
  ...
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}
Copy the code

Instead of “X.X.X”, you can find the latest version number of the Coroutines library on the Kotlin Coroutines publishing page.

Coroutines and RxJava

If you use RxJava in your current code base, you can use the Kotlin-Coroutines-rx library to integrate with coroutines.

4. Coroutines in Kotlin

On Android, avoiding blocking the main thread is essential. The main thread is the thread that handles all UI updates. It is also the thread that calls all click handlers and other UI callbacks. Therefore, it must run smoothly to ensure a great user experience.

In order for your application to display to the user without any visible pauses, the main thread must update the screen every 16 milliseconds or more, about 60 frames per second. Many common tasks take longer than that, such as parsing large JSON datasets, writing data to a database, or fetching data from the network. Therefore, calling such code from the main thread can cause the application to pause, stall, or even freeze. If you block the main thread too long, the Application may even crash and display the Application Not Responding dialog box.

The callback pattern

One mode for a long-running task without blocking the main thread is a callback. By using callbacks, you can start long-running tasks on background threads. When the task completes, a callback is invoked to notify you of the result on the main thread.

Look at an example of the callback pattern.

// Slow request with callbacks
@UiThread
fun makeNetworkRequest(a) {
    // The slow network request runs on another thread
    slowFetch { result ->
        // When the result is ready, this callback will get the result
        show(result)
    }
    // makeNetworkRequest() exits after calling slowFetch without waiting for the result
}
Copy the code

Because this code is annotated with @uithRead, it must run fast enough to execute on the main thread. This means that it needs to return very quickly so that the next screen update is not delayed. However, because slowFetch takes seconds or even minutes to complete, the main thread cannot wait for results. The show(result) callback allows slowFetch to run on background threads and return results when it is ready.

Use coroutines to remove callbacks

Callbacks are a good model, but they have some downsides. Code that makes heavy use of callbacks becomes hard to read and reason about. In addition, callbacks do not allow the use of certain language features, such as exceptions.

The Kotlin coroutine lets you convert callback-based code to sequential code. Code written sequentially is usually easier to read and can even use language features such as exceptions.

In the end, they do exactly the same thing: wait for results from long-running tasks and continue. In code, however, they look very different.

The keyword suspend is Kotlin’s way of marking functions or function types that can be used with coroutines. When a coroutine calls a function marked suspend, it does not block as a normal function call until the function returns, but suspends execution (Suspends) until the result is ready and then continues from where the result is cut off. When it suspends waiting for results, it unblocks the thread on which it is running so that other functions or coroutines can run.

For example, in the code below, makeNetworkRequest() and slowFetch() are both suspend functions.

// Slow request with coroutines
@UiThread
suspend fun makeNetworkRequest(a) {
    // slowFetch is another suspend function so instead of 
    // blocking the main thread makeNetworkRequest will `suspend` until the result is
    // ready
    val result = slowFetch()
    // continue to execute after the result is ready
    show(result)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(a): SlowResult { ... }
Copy the code

Just like the callback version, makeNetworkRequest must be returned from the main thread immediately because it is marked @uithRead. This means that in general it cannot call blocking methods like slowFetch. This is where the suspend keyword works its magic.

Important: The suspend keyword does not specify a running thread for your code. Suspend functions can be run on background threads or the main thread.

Coroutine code achieves the same result of unblocking the current thread in less code than callback-based code. Because of its sequential style, it is easy to link several long-running tasks without having to create multiple callbacks. For example, the code that gets the results from two network endpoints and saves them to a database can be written as a function in a coroutine, without callbacks. Like this:

// Request data from network and save it to database with coroutines

// Because of the @WorkerThread, this function cannot be called on the
// main thread without causing an error.
@WorkerThread
suspend fun makeNetworkRequest(a) {
    // slowFetch and anotherFetch are suspend functions
    val slow = slowFetch()
    val another = anotherFetch()
    // save is a regular function and will block this thread
    database.save(slow, another)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(a): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(a): AnotherResult { ... }
Copy the code

Coroutines by another name The async and await modes of other languages are based on coroutines. If you are familiar with this pattern, the suspend keyword is similar to async. But in Kotlin, await() is implicit when the pending function is called.

Kotlin has a method deferred.await () to wait for the result of a coroutine started from an asynchronous builder.

5. Use coroutines to control UI

In this exercise, you will write a coroutine to display a message after a delay. First, make sure you have the module Start turned on in Android Studio.

To understandCoroutineScope

In Kotlin, all coroutines run inside the CoroutineScope. The scope controls the life cycle of the coroutine through its Job. When you cancel the Job of a Scope, it cancels all coroutines started in that Scope. On Android, you can use scopes to cancel all running coroutines, for example, when a user navigates away from an Activity or Fragment. The scope also allows you to specify the default Dispatcher. The scheduler controls which thread runs the coroutine. For coroutines started by the UI, it is usually correct to start them on dispatchers. Main, which is the Main thread on Android. Coroutines launched on dispatchers. Main do not block the Main thread when suspended. Since the ViewModel coroutine almost always updates the UI on the main thread, starting the coroutine on the main thread saves extra thread switching. Coroutines started on the main thread can switch schedulers at any time after starting. For example, it can use another scheduler to parse large JSON results from the main thread.

Coroutines provide main-safety

Because coroutines can easily switch threads and send results back to the original thread at any time, it’s a good idea to start uI-related coroutines on the main thread.

Libraries like Room and Retrofit provide out-of-the-box primary security when using coroutines, so you don’t need to manage threads to make network or database calls. This usually results in simpler code.

However, blocking code such as sorting lists or reading files still requires explicit code to create primary security, even when coroutines are used. This is also true if you are using a network or database library that (yet) does not support coroutines.

useviewModelScope

The AndroidX lifecycle- ViewModel-ktx library adds a CoroutineScope to the ViewModels, which is configured to start uI-related coroutines. To use this library, you must include it in your project’s build.gradle (module: start) file. This has already been done in the Codelab project.

dependencies {
  ...
  // replace x.x.x with latest version
  implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x"
}
Copy the code

The library adds a viewModelScope as an extension to the ViewModel class. This scope is bound to dispatchers. Main and is automatically cancelled when the ViewModel is cleared.

Switch from thread to coroutine

Find the next TODO in mainViewModel.kt with the following code:

MainViewModel.kt

/**
* Wait one second then update the tap count.
*/
private fun updateTaps(a) {
   // TODO: Convert updateTaps to use coroutines
   tapCount++
   BACKGROUND.submit {
       Thread.sleep(1 _000)
       _taps.postValue("$tapCount taps")}}Copy the code

This code runs in BACKGROUND threads using the BACKGROUND ExecutorService (defined in util/ executor.kt). Because Sleep blocks the current thread, it freezes the UI if called on the main thread. One second after the user clicks on the main view, it requests a Snackbar.

You can see this by removing BACKGROUND from the code and running it again. The spinner in the loaded state will not be displayed, and after a second everything will “jump” to the final state.

MainViewModel.kt

/**
* Wait one second then update the tap count.
*/
private fun updateTaps(a) {
   // TODO: Convert updateTaps to use coroutines
   tapCount++
   Thread.sleep(1 _000)
   _taps.postValue("$tapCount taps")}Copy the code

Replace update Aps with this coroutine based code to do the same thing. You must import launch and delay.

MainViewModel.kt

/**
* Wait one second then display a snackbar.
*/
fun updateTaps(a) {
   // launch a coroutine in viewModelScope
   viewModelScope.launch {
       tapCount++
       // suspend this coroutine for one second
       delay(1 _000)
       // resume in the main dispatcher
       // _snackbar.value can be called directly from main thread
       _taps.postValue("$tapCount taps")}}Copy the code

This code does the same thing, waiting a second before displaying the snackbar. However, there are some important differences:

  1. viewModelScope. launchWill be inviewModelScopeStart a coroutine in. This means that when we pass toviewModelScopeJobIs cancelled when thisJobAll coroutines in the/scope will be cancelled. If the user is indelayIf you leave the Activity before returning, it is destroyedViewModelCalled when theonCleared, this coroutine is automatically cancelled.
  2. Due to theviewModelScopeHas a default schedulerDispatchers.Main, so the coroutine will start in the main thread. We’ll see how to use different threads later.
  3. functiondelayIs a suspend function. This is in Android Studio by the left side of the binding lineIcon display. Even though this coroutine is running on the main thread,delayIt doesn’t block the thread for a second. Instead, the scheduler schedules the coroutine to resume in the next statement within a second.

Keep running it. When you click on the main view, you should see a Snackbar a second later.

6. Pass behavior test coroutines

In this exercise, you will write a test for the code you just wrote. This exercise shows you how to use the Kotlinx-Coroutines-test library to test coroutines running on dispatchers.main. Later in this Codelab, you will implement a test that interacts directly with coroutines.

The Kotlinx-Coroutines-test library used in this section is marked as experimental and subject to significant changes prior to release.

View existing code

Open mainViewModelTest.kt in the androidTest folder.

MainViewModelTest.kt

class MainViewModelTest {
   @get:Rule
   val coroutineScope =  MainCoroutineScopeRule()
   @get:Rule
   val instantTaskExecutorRule = InstantTaskExecutorRule()

   lateinit var subject: MainViewModel

   @Before
   fun setup(a) {
       subject = MainViewModel(
           TitleRepository(
                   MainNetworkFake("OK"),
                   TitleDaoFake("initial")))}}Copy the code

Rules are a way to run code before and after tests are executed in JUnit. Two rules allow us to test the MainViewModel in off-device tests:

  1. InstantTaskExecutorRuleIs a JUnit rule for configurationLiveDataTo execute each task synchronously
  2. MainCoroutineScopeRuleIs a custom rule in this code base for configurationDispatchers.MainTo use fromkotlinx-coroutines-testTestCoroutineDispatcher. This allows tests to push a virtual clock for testing and allows code to be used in unit testsDispatchers.Main.

In the Setup method, a new instance of MainViewModel is created using test forgeries — these are forged implementations of the network and database provided in the startup code to help write tests without using a real network or database.

For this test, Fakes only need to satisfy the MainViewModel dependency. Later in this code lab, you will update Fakes to support coroutines.

Write a test that controls the coroutine

Add a new test to make sure the click is updated one second after the main view is clicked:

MainViewModelTest.kt

@Test
fun whenMainClicked_updatesTaps(a) {
   subject.onMainViewClicked()
   Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("0 taps")
   coroutineScope.advanceTimeBy(1000)
   Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("1 taps")}Copy the code

By calling onMainViewClicked, the coroutine we just created will be launched. This test checks that the clicked text remains “0 clicked” immediately after onMainViewClicked, then updated to “1 clicked” one second later.

This test uses virtual-time to control the execution of coroutines launched from onMainViewClicked. MainCoroutineScopeRule allows you to pause, resume, or control the execution of coroutines started on dispatchers.main. Here we call AdvanceTimeBy(1_000), which causes the main scheduler to immediately execute the coroutine that is scheduled to recover after 1 second.

This test is completely deterministic, which means it will always be executed the same way. And because it has complete control over the execution of coroutines launched on Dispatchers.main, it doesn’t have to wait a second to set values.

Running existing tests

  1. Right-click the class name in the editorMainViewModelTestTo open the context menu. Select from the context menu RunMainViewModelTest
  2. For later runs, you can do this in the toolbarSelect this test configuration from the configuration next to the button. By default, this configuration will be calledMainViewModelTest.

You should see the test pass! It should take less than a second to run.

7. Switch from callback to coroutine

In this step, you will begin transforming the repository to use coroutines. To do this, we will add coroutines to ViewModel, Repository, Room, and Retrofit.

Before we switch to using coroutines, it is a good idea to understand what each part of the architecture is responsible for.

  1. MainDatabaseuseRoomImplements a database for saving and loadingTitle.
  2. MainNetworkImplements a web API to get new titles. It USESRetrofitIn order to getTitle.RetrofitIs configured to return random errors or mock data, but otherwise behaves as if it is making a real network request.
  3. TitleRepositoryImplements an API for fetching or refreshing data by combining data from the network and databaseTitle.
  4. MainViewModelRepresents the state of the screen and handles events. When the user clicks on the screen, it tells the repository to refreshTitle.

Since network requests are driven by UI events, we want to start coroutines based on them, so the natural place to start using coroutines is in the ViewModel.

The callback version

Open mainViewModel.kt to see the declaration of refreshTitle.

MainViewModel.kt

/**
* Update title text via this LiveData
*/
val title = repository.title


// ... other code ...


/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle(a) {
   // TODO: Convert refreshTitle to use coroutines
   _spinner.value = true
   repository.refreshTitleWithCallbacks(object: TitleRefreshCallback {
       override fun onCompleted(a) {
           _spinner.postValue(false)}override fun onError(cause: Throwable) {
           _snackBar.postValue(cause.message)
           _spinner.postValue(false)}}}Copy the code

This function is called each time the user clicks on the screen – it causes the repository to refresh the title and write the new title to the database.

This implementation uses callbacks to do several things:

  • Before starting the query, it displays a file with_spinner.value = trueOf the load state ofspinner
  • When it gets the result, it uses_spinner.value = falseClears the loaded statespinner
  • If something goes wrong, it tells yousnackbarDisplays and clears the loaded statespinner

Notice that the onCompleted callback does not pass Title. Since we are writing all titles to the Room database, the UI updates the current Title by observing the LiveData of the Room update.

In updating to the coroutine, we will keep exactly the same behavior. Using an observable data source like the Room database to automatically keep the UI up to date is a good model.

object: TitleRefreshCallbackWhat does that mean?

This is the way to build anonymous classes in Kotlin. It creates a new object that implements the TitleRefreshCallback.

Coroutines version

Let’s rewrite refreshTitle with a coroutine!

Since we need it immediately, let’s create an empty suspend function in our repository (titlerespository.kt). Defines a new function that uses the suspend operator to tell Kotlin that it works with coroutines.

TitleRepository.kt

suspend fun refreshTitle(a) {
    // TODO: Refresh from network and write to database
    delay(500)}Copy the code

Once this Codelab is complete, you will update it to get the new Title using Retrofit and Room and write it to the database using coroutines. Now, it only takes 500 milliseconds to pretend to work and continue.

In MainViewModel, replace the callback version of refreshTitle with the one that started the new coroutine:

MainViewModel.kt

/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle(a) {
   viewModelScope.launch {
       try {
           _spinner.value = true
           repository.refreshTitle()
       } catch (error: TitleRefreshError) {
           _snackBar.value = error.message
       } finally {
           _spinner.value = false}}}Copy the code

Let’s step through this function:

viewModelScope.launch {
Copy the code

Just like a coroutine that updates the number of clicks, start a new coroutine in viewModelScope. This will use dispatchers. Main, which is ok. Even though refreshTitle makes network requests and database queries, it can use coroutines to expose the main-Safe interface. This means it is safe to call from the main thread.

Because we are using viewModelScope, the work started by the coroutine will be cancelled automatically when the user leaves the screen. This means that it does not make additional network requests or database queries.

When creating coroutines from non-coroutines, start with Launch.

This way, if they throw an uncaught exception, it will be automatically propagated to the uncaught exception handler (crashing the application by default). A coroutine started with async does not throw an exception to its caller until you call await. However, you can only call await from inside the coroutine because it is a pending function.

Once inside the coroutine, you can use launch or Async to start the child coroutine. Use launch when you do not return a result, and async when you return a result.

The next few lines actually call refreshTitle in Repository.

try {
    _spinner.value = true
    repository.refreshTitle()
}
Copy the code

Before this coroutine does anything, it starts loading spinner — it then calls refreshTitle just like a regular function. However, because refreshTitle is a suspended function, it is executed differently than a normal function.

We don’t have to pass the callback. The coroutine will hang until it is restored by refreshTitle. Although it looks just like a normal blocking function call, it automatically waits for network and database queries to complete and then continues without blocking the main thread.

} catch (error: TitleRefreshError) {
    _snackBar.value = error.message
} finally {
    _spinner.value = false
}
Copy the code

An exception in a suspended function works just like an error in a regular function. If an error is thrown in a suspended function, it will be thrown to the caller. So, even if they execute differently, you can handle them with regular try/catch blocks. This is useful because it lets you rely on built-in language support for error handling, rather than building custom error handling for each callback.

Also, if you throw an exception from a coroutine — by default, the coroutine cancels its parent. This means it’s easy to cancel several related tasks at the same time.

Then, in the finally block, we can ensure that the Spinner is always closed after the query runs.

What happens to uncaught exceptions

An uncaught exception in a coroutine is similar to an uncaught exception in non-coroutine code. By default, they take out the coroutine’s Job and inform the parent coroutine that they should cancel themselves. If there is no coroutine to handle the exception, it will eventually be passed to an uncaught exception handler on the CoroutineScope.

By default, uncaught exceptions are sent to the uncaught exception handler of the thread on the JVM. You can define by providing CoroutineExceptionHandler from this behavior.

Start the configuration by selecting and then pressingRun the application again, and when you click anywhere, you should see oneloading spinnerTitleWill remain unchanged as we have not yet connected to our network or database.

8. Create from blocking codeThe main safetyThe main-safe function

In this exercise, you learn how to switch the thread on which the coroutine runs to implement the working version of TitleRepository.

To viewrefreshTitleExisting callback code in

Open titlerepository.kt and look at the existing callback-based implementation.

TitleRepository.kt

// TitleRepository.kt

fun refreshTitleWithCallbacks(titleRefreshCallback: TitleRefreshCallback) {
   // This request will be run on a background thread by retrofit
   BACKGROUND.submit {
       try {
           // Make network request using a blocking call
           val result = network.fetchNextTitle().execute()
           if (result.isSuccessful) {
               // Save it to databasetitleDao.insertTitle(Title(result.body()!!) )// Inform the caller the refresh is completed
               titleRefreshCallback.onCompleted()
           } else {
               // If it's not successful, inform the callback of the error
               titleRefreshCallback.onError(
                       TitleRefreshError("Unable to refresh title".null))}}catch (cause: Throwable) {
           // If anything throws an exception, inform the caller
           titleRefreshCallback.onError(
                   TitleRefreshError("Unable to refresh title", cause))
       }
   }
}
Copy the code

In TitleRepository. Kt, refreshTitleWithCallbacks methods are implemented through callbacks, to load and error status to the caller.

This function does a lot of things to implement the refresh.

  1. useBACKGROUND ExecutorServiceSwitch to another thread
  2. Using blockedexecute()Way to runfetchNextTitleNetwork request. This will run the network request in the current thread, in this caseBACKGROUNDOne thread.
  3. If the result is successful, useinsertTitleSave it to the database and callonCompleted()Methods.
  4. If the result is unsuccessful or an exception occurs, callonErrorMethod tells the caller that the refresh failed.

This callback – based implementation is main-safe because it does not block the main thread. However, it must use a callback to notify the caller when the work is done. It also invokes the callback on the BACKGROUND thread it switches.

Block the call from the coroutine call

Without introducing coroutines to the network or database, we can use coroutines to make this code main-safe. This will get rid of the callback and allow us to pass the result back to the thread that originally called it.

You can use this pattern anytime you need to perform blocking or CPU intensive work from within the coroutine, such as sorting and filtering large lists or reading from disk.

This pattern is used to integrate with blocking apis in code or perform CPU-intensive work. If possible, it is best to use regular suspend functions in libraries such as Room or Retrofit.

To switch between any scheduler, the coroutine uses withContext. Calling withContext switches to another scheduler, just for lambda, and then goes back to the scheduler that called it with the result of that lambda.

By Default, Kotlin coroutines provide three dispatchers: Main, IO and Default. The IO scheduler is optimized for IO work, such as reading from the network or disk, while the Default scheduler is optimized for CPU-intensive tasks.

TitleRepository.kt

suspend fun refreshTitle(a) {
   // interact with *blocking* network and IO calls from a coroutine
   withContext(Dispatchers.IO) {
       val result = try {
           // Make network request using a blocking call
           network.fetchNextTitle().execute()
       } catch (cause: Throwable) {
           // If the network throws an exception, inform the caller
           throw TitleRefreshError("Unable to refresh title", cause)
       }
      
       if (result.isSuccessful) {
           // Save it to database
           titleDao.insertTitle(Title(result.body()!!))
       } else {
           // If it's not successful, inform the callback of the error
           throw TitleRefreshError("Unable to refresh title".null)}}}Copy the code

This implementation uses blocking calls to the network and database — but it’s still a bit simpler than the callback version.

This code still uses blocking calls. Calls to execute() and insertTitle(…) Blocks the thread in which the coroutine is running. However, by switching to Dispatchers.IO with withContext, we block one of the threads in the IO scheduler. The coroutine that calls it (possibly running on dispatchers.main) will be suspended until the withContext lambda completes.

There are two important differences from the callback version:

  1. withContextReturns the result to the callerDispatcher, in this case, isDispatchers.Main. The callback version invokes callbacks on threads in the daemon service.
  2. The caller does not have to pass the callback to this function. They can rely on suspend and resume to get results or errors.

Advanced tips

This code does not support coroutine cancellation, but it does! Coroutine cancellation is cooperative. This means that your code needs to explicitly check for cancellation, which happens every time you call a function in Kotlinx-Coroutines.

Because the withContext block only calls the blocking call, it won’t be canceled until it’s returned from the withContext.

To solve this problem, you can periodically call yield to give other coroutines a chance to run and check for cancellation. Here, you will add a call to yield between the network request and the database query. Then, if the coroutine is fetched during a network request, the results are not saved to the database.

You can also explicitly check for cancellation, which should be done when making low-level coroutine interfaces.

Run the application again

If you run the application again, you will see the new coroutine based implementation loading results from the network!

9. Room & RetrofitThe coroutines

To continue coroutine integration, we’ll use the support for suspend functions in stable Room and Retrofit, and then simplify the code we just wrote significantly by using suspend functions.

RoomThe coroutines

First open mainDatabase.kt and make insertTitle a suspend function:

MainDatabase.kt

// add the suspend modifier to the existing insertTitle

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTitle(title: Title)
Copy the code

When you do this, Room will make your query main-safe and automatically execute it on the background thread. However, this also means that you can only call this query from within the coroutine.

And – that’s all you need to do to use coroutines in Room. Very beautiful.

RetrofitThe coroutines

Let’s look at how to integrate coroutines with Retrofit. Open mainNetwork.kt and change fetchNextTitle to suspend. Also change the return type from Call

to String.

Suspension support requires Retrofit 2.6.0 or later.

MainNetwork.kt

// add suspend modifier to the existing fetchNextTitle
// change return type from Call<String> to String

interface MainNetwork {
   @GET("next_title.json")
   suspend fun fetchNextTitle(a): String
}
Copy the code

To use suspend functions in Retrofit, you must do two things:

  1. Add to functionsuspendThe modifier
  2. Delete from the return typeCallThe wrapper. So here we go backString, but you can also return a complex JSON-backed type. If you still want to provide onRetrofitAccess to the full results that you can return from the pending functionResult<String>Rather thanString.

Retrofit will automatically make suspended functions main-safe, so you can call them directly from dispatchers.main.

Both Room and Retrofit make pause main-safe.

It is safe to call these suspended functions from dispatchers.main, even if they are fetched from the network and written to the database.

Both Room and Retrofit use custom schedulers, not dispatchers.io.

Room runs the coroutine using the configured default query and transaction executor.

Retrofit creates a new Call object in the background and calls enQueue on it to send the request asynchronously.

useRoomRetrofit

Room and Retrofit now support suspend functionality that we can use from our repository. Open up titlerepository.kt and see how using suspend functions greatly simplifies logic, even compared to the blocking version:

TitleRepository.kt

suspend fun refreshTitle(a) {
   try {
       // Make network request using a blocking call
       val result = network.fetchNextTitle()
       titleDao.insertTitle(Title(result))
   } catch (cause: Throwable) {
       // If anything throws an exception, inform the caller
       throw TitleRefreshError("Unable to refresh title", cause)
   }
}
Copy the code

Wow, it’s much shorter. What happened? It turns out that relying on suspend and resume makes code shorter. Retrofit allows us to use return types like String or User objects instead of Call here. This is safe because, inside the suspended function, Retrofit is able to run the network request on the background thread and resume the coroutine when the call completes.

And even better, we get rid of the withContext. Since both Room and Retrofit provide main-safe suspension functions, it is safe to orchestral this asynchronous work from dispatchers.main.

You do not need to use withContext to call the main-safe suspend function.

As a general rule, you should make sure that the suspend functions you write in your application are main-safe. This makes it safe to call them from any scheduler, even dispatchers.main.

Fix compiler errors

Moving to a coroutine does involve changing the signature of a function, because you cannot call a suspended function from a regular function. When you add the suspend modifier to this step, compiler errors are generated that show what happens if you change the function to suspend in a real project.

Check the project and fix the compiler error by changing the function to suspend creation. Here’s a quick solution to each problem:

TestingFakes.kt

Update test Fakes to support new suspend modifiers.

TitleDaoFake

  • Press Alt-Enter (option-Enter on Mac) to add the suspend modifier to all functions in the hierarchy

MainNetworkFake

  1. Press Alt-Enter to add the suspend modifier to all functions in the hierarchy
  2. I’m going to replace it with this functionfetchNextTitle
override suspend fun fetchNextTitle(a) = result
Copy the code

MainNetworkCompletableFake

  1. Press Alt-Enter to add the suspend modifier to all functions in the hierarchy
  2. I’m going to replace it with this functionfetchNextTitle
override suspend fun fetchNextTitle(a) = completable.await()
Copy the code

TitleRepository.kt

  • deleterefreshTitleWithCallbacksFunction because it is no longer used.

Run the application

Run the application again, and once compiled, you’ll see it using coroutines to load data from ViewModel to Room and Retrofit all the time!

10. Test coroutines directly

In this exercise, you will write a test that calls the suspend function directly.

Because refreshTitle is exposed as a public API, you will test directly to show how to call the coroutine function from the test.

Here is the refreshTitle function that you implemented in the previous exercise:

TitleRepository.kt

suspend fun refreshTitle(a) {
   try {
       // Make network request using a blocking call
       val result = network.fetchNextTitle()
       titleDao.insertTitle(Title(result))
   } catch (cause: Throwable) {
       // If anything throws an exception, inform the caller
       throw TitleRefreshError("Unable to refresh title", cause)
   }
}
Copy the code

Write a test that calls a pending function

Open titlerepositorytest.kt in the test folder with two TODOS.

Try calling refreshTitle from the first test whenRefreshTitleSuccess_insertsRows.

@Test
fun whenRefreshTitleSuccess_insertsRows(a) {
   val subject = TitleRepository(
       MainNetworkFake("OK"),
       TitleDaoFake("title")
   )

   subject.refreshTitle()
}
Copy the code

Since refreshTitle is a suspended function, Kotlin doesn’t know how to call it other than from a coroutine or another suspended function, and you get a compiler error such as “the suspended function refreshTitle should only be called from one coroutine or another suspended function”.

The test runner knows nothing about coroutines, so we cannot set this test as a suspend function. We can start the coroutine using the CoroutineScope as we did in the ViewModel, but the testing needs to be done by running the coroutine before it returns. Once the test function returns, the test is over. Coroutines that start with launch are asynchronous code that may be completed at some point in the future. Therefore, to test the asynchronous code, you need some way to tell the test to wait for the coroutine to complete. Since launch is a non-blocking call, this means that it returns immediately and can continue running the coroutine after the function returns — it cannot be used for testing. Such as:

@Test
fun whenRefreshTitleSuccess_insertsRows(a) {
   val subject = TitleRepository(
       MainNetworkFake("OK"),
       TitleDaoFake("title"))// launch starts a coroutine then immediately returns
   GlobalScope.launch {
       // since this is asynchronous code, this may be called *after* the test completes
       subject.refreshTitle()
   }
   // test function returns immediately, and
   // doesn't see the results of refreshTitle
}
Copy the code

This test sometimes fails. The call to Launch is returned immediately and executed concurrently with the rest of the test case. The test has no way of knowing whether refreshTitle has been run – and any assertions such as checking whether the database has been updated are unstable. Also, if refreshTitle throws an exception, it is not thrown in the test call stack. Instead, it will be thrown into GlobalScope’s uncaught exception handler.

The library kotlinx-Coroutines-test has the runBlockingTest function, which blocks when a pending function is called. RunBlockingTest executes immediately by default when it calls a pending function or starts a new coroutine. You can think of it as a way to convert pending functions and coroutines into normal function calls.

In addition, runBlockingTest will rethrow uncaught exceptions for you. This makes it easier to test when coroutines throw exceptions.

Important: The function runBlockingTest will always block the caller, just like a regular function call. Coroutines will run synchronously on the same thread. You should avoid using runBlocking and runBlockingTest in your application code in favor of a launch that returns immediately.

RunBlockingTest can only be used in tests because it executes coroutines in a test-controlled manner, while runBlocking can be used to provide a blocking interface to coroutines

Use a coroutine to implement the tests

Wrap the call to refreshTitle with runBlockingTest and remove the GlobalScope.launch wrapper from subject.refreshTitle().

TitleRepositoryTest.kt

@Test
fun whenRefreshTitleSuccess_insertsRows(a) = runBlockingTest {
   val titleDao = TitleDaoFake("title")
   val subject = TitleRepository(
           MainNetworkFake("OK"),
           titleDao
   )

   subject.refreshTitle()
   Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK")}Copy the code

This test uses the supplied fakes to check whether refreshTitle inserts “OK” into the database.

When a test calls runBlockingTest, it blocks until the coroutine that runBlockingTest started completes. Then internally, when we call refreshTitle, it uses the normal suspend and restore mechanism to wait for the database rows to be added to our FAKE.

When the test coroutine completes, runBlockingTest returns.

Write a timeout test

We want to add a short timeout for network requests. Let’s write the test first and implement the timeout. Create a new test:

TitleRepositoryTest.kt

@Test(expected = TitleRefreshError::class)
fun whenRefreshTitleTimeout_throws(a) = runBlockingTest {
   val network = MainNetworkCompletableFake()
   val subject = TitleRepository(
           network,
           TitleDaoFake("title")
   )

   launch {
       subject.refreshTitle()
   }

   advanceTimeBy(5 _000)}Copy the code

This test using the provided fake MainNetworkCompletableFake, this is a fake, network, to suspend the caller, until the test to continue them. When refreshTitle tries to make a network request, it will hang forever because we are testing for a timeout.

It then launches a separate coroutine to call refreshTitle. This is a key part of the test timeout, which should occur in a different coroutine than the one created by runBlockingTest. By doing so, we can call the next line, advancedTimeBy(5_000), which will advance the time by 5 seconds and cause another coroutine to time out.

This is a complete timeout test that will pass once we implement the timeout.

Now run it and see what happens:

Caused by: kotlinx.coroutines.test.UncompletedCoroutinesError: Test finished with active jobs: ["... ]Copy the code

One of the features of runBlockingTest is that it does not let you leak coroutines after the test is complete. If there are any unfinished coroutines, such as our startup coroutine, it will cause the test to fail at the end of the test.

A timeout

Open TitleRepository and add a 5-second timeout for network capture. You can do this using the withTimeout function:

TitleRepository.kt

suspend fun refreshTitle(a) {
   try {
       // Make network request using a blocking call
       val result = withTimeout(5 _000) {
           network.fetchNextTitle()
       }
       titleDao.insertTitle(Title(result))
   } catch (cause: Throwable) {
       // If anything throws an exception, inform the caller
       throw TitleRefreshError("Unable to refresh title", cause)
   }
}
Copy the code

Run the tests. When you run the tests, you should see that all the tests pass!

In the next exercise, you will learn how to write higher-order functions using coroutines.

RunBlockingTest relies on TestCoroutineDispatcher to control coroutines.

Therefore, it is a good idea to inject TestCoroutineDispatcher or TestCoroutineScope when using runBlockingTest. This has the effect of making coroutines single threaded and provides the ability to explicitly control all coroutines during testing.

If you do not want to change the behavior of coroutines (for example, in integration tests), you can use runBlocking with the default implementation of all schedulers.

RunBlockingTest is experimental and currently has a bug that causes the test to fail if the coroutine switches to a scheduler that executes the coroutine on another thread. The final stable version is not expected to have this error.

11. Use coroutines in higher-order functions

In this exercise, you will refactor refreshTitle in the MainViewModel to use the common data load function. This will teach you how to build higher-order functions that use coroutines.

The current implementation of refreshTitle is valid, but we can create a generic data loading coroutine that always displays a spinner. This can be helpful in a code base that loads data in response to multiple events and wants to ensure that loading spinner is always displayed.

Looking at the current implementation, every line except repository.refreshTitle() is a boilerplate for displaying spinner and displaying errors.

// MainViewModel.kt

fun refreshTitle(a) {
   viewModelScope.launch {
       try {
           _spinner.value = true
           // this is the only part that changes between sources
           repository.refreshTitle() 
       } catch (error: TitleRefreshError) {
           _snackBar.value = error.message
       } finally {
           _spinner.value = false}}}Copy the code

Important note: Although we only use viewModelScope in this Codelab, you can usually add scope wherever it makes sense. Don’t forget to cancel it if you no longer need it.

For example, you can declare one in the RecyclerView Adapter to perform the DiffUtil operation.

Use coroutines in higher-order functions

Add this code to mainViewModel.kt

private fun launchDataLoad(block: suspend() - >Unit): Job {
   return viewModelScope.launch {
       try {
           _spinner.value = true
           block()
       } catch (error: TitleRefreshError) {
           _snackBar.value = error.message
       } finally {
           _spinner.value = false}}}Copy the code

Now refactor refreshTitle() to use this higher-order function.

MainViewModel.kt

fun refreshTitle(a) {
   launchDataLoad {
       repository.refreshTitle()
   }
}
Copy the code

By abstracting the logic for loading the spinner and displaying the error, we simplify the actual code required to load the data. Displaying a spinner or displaying an error can easily be generalized to any data load, and the actual data source and target need to be specified each time.

To build this abstraction, launchDataLoad requires a parameter block, which is a suspended lambda. A suspended lambda allows you to call a suspended function. This is how Kotlin implements the coroutine builders launch and runBlocking that we’ve been using in this Codelab.

// suspend lambda

block: suspend() - >Unit

Copy the code

To make a suspended lambda, start with the suspend keyword. The function arrow and return type Unit complete the declaration.

You don’t usually have to declare your own suspended lambda, but they help to create such abstractions to encapsulate repetitive logic!

In 12.WorkManagerCoroutines are used in

In this exercise, you will learn how to use coroutine based code in WorkManager.

What is theWorkManager

Android has a number of options for deferring background work. This exercise shows you how to integrate WorkManager with coroutines. WorkManager is a compatible, flexible, and simple library for delayable background work. WorkManager is the recommended solution for these use cases on Android.

WorkManager, part of Android Jetpack, is an architectural component for back-end work that requires a combination of opportunism and guaranteed execution. Opportunistic execution means that WorkManager completes your background work as quickly as possible. Guaranteed execution means that WorkManager will handle the logic to start working in various cases, even if you leave the application.

Therefore, WorkManager is a good choice for tasks that must eventually be completed.

Some examples of tasks that make good use of WorkManager:

  • Upload the log
  • Apply a filter to the image and save the image
  • Periodically synchronize local data with the network

To learn more about WorkManager, check out the documentation.

inWorkManagerCoroutines are used in

WorkManager provides different implementations of its base ListenableWorker class for different use cases.

The simplest Worker class allows us to perform some synchronization through the WorkManager. However, so far we have converted our code base to use coroutines and suspend functions, and the best way to use WorkManager is through the CoroutineWorker class, which allows our doWork() function to be defined as a suspend function.

First, open RefreshMainDataWork. It has extended CoroutineWorker and you need to implement doWork.

In the suspend doWork function, refreshTitle() is called from the repository and returns the appropriate result!

After completing TODO, the code will look like this:

override suspend fun doWork(a): Result {
   val database = getDatabase(applicationContext)
   val repository = TitleRepository(network, database.titleDao)

   return try {
       repository.refreshTitle()
       Result.success()
   } catch (error: TitleRefreshError) {
       Result.failure()
   }
}
Copy the code

Note that coroutineworker.dowork () is a suspend function. Unlike the simpler Worker class, this code does not run on the Executor specified in the WorkManager configuration, but instead uses the scheduler in the coroutineContext member (dispatchers.default).

Test ourCoroutineWorker

No code base should be complete without tests.

WorkManager provides several different ways to test your Worker classes, and to learn more about the original test infrastructure, you can read the documentation.

WorkManager V2.1 introduces a new set of apis to support a simpler way to test the ListenableWorker class, thereby supporting CoroutineWorker. In our code, we will use one of these new apis: TestListenableWorkerBuilder.

To add our new test, update the RefreshMainDataWorkTest file under the androidTest folder.

The document reads:

package com.example.android.kotlincoroutines.main

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestListenableWorkerBuilder
import com.example.android.kotlincoroutines.fakes.MainNetworkFake
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4


@RunWith(JUnit4::class)
class RefreshMainDataWorkTest {

@Test
fun testRefreshMainDataWork(a) {
   val fakeNetwork = MainNetworkFake("OK")

   val context = ApplicationProvider.getApplicationContext<Context>()
   val worker = TestListenableWorkerBuilder<RefreshMainDataWork>(context)
           .setWorkerFactory(RefreshMainDataWork.Factory(fakeNetwork))
           .build()

   // Start the work synchronously
   val result = worker.startWork().get()

   assertThat(result).isEqualTo(Result.success())
}

}
Copy the code

Before we start testing, we tell the WorkManager about the factory so that we can inject the FAKE network.

Test itself USES TestListenableWorkerBuilder create our work procedure, and then we can call the startWork () method to run it.

WorkManager is just one example of how coroutines can be used to simplify API design.

13. Congratulations!

In this Codelab, we cover the basics you need to get started using coroutines in your application!

We cover:

  1. How to get from UI andWorkManagerJobs integrate coroutines into Android applications to simplify asynchronous programming,
  2. How to inViewModelCoroutines are used to retrieve data from the network and save it to the database without blocking the main thread.
  3. And how toViewModelCancel all coroutines when complete.

To test coroutine based code, we cover the behavior of the test as well as calling suspended functions directly from the test.

Learn more

See “Learn Advanced Coroutines with Kotlin Flow and LiveData” Codelab for more advanced coroutines on Android.

For more information on cancellations and Exceptions in Coroutines, check out this series: Part 1: Coroutines, Part 2: Cancellation in Coroutines, and Part 3: Exceptions in Coroutines.

Kotlin coroutines have many features not covered by this Codelab. If you’re interested in learning more about Kotlin coroutines, read JetBrains’ guide to coroutines. Also see “Improving Application Performance with Kotlin Coroutines” to learn more about the usage patterns of Android coroutines.