Async tasks with Kotlin Coroutines
An example using Firestore with Android Architecture Components
Essentially, coroutines are light-weight threads. They are launched with launch coroutine builder.
Kotlin Coroutines are a powerful and clear way to work with async tasks, like a backend call or a delay. Essentially, they are very simple. That’s the reason why we used them in our pet project. Thanks to their clarity and concise syntax we can create light threads, that use suspend methods and let us express our async tasks like sync tasks.
Before getting to our core example, let’s see the probably simplest example of a coroutine, the delay:
We are going to use it with Android Architecture Components for Firestore actions. In a way that we can test. We are going to compare two solutions to solve the same async challenge with and without coroutines. And we will look at data synchronization between firestore and room, our local databases.
The project in which this is implemented is about helping users find their lost pets. If you want to see other interesting approaches that were born from this pet project, please check our posts about dependency injection with Koin and Deferred authentication.
UI Coroutines for Android
When the coroutine is launched on an activity we need to give the context. And to do this we need to add another dependency. We have been using kotlinx-coroutines-core and we are going to use kotlinx-coroutines-android too.
Because we are using the android architecture components, the activity is not known from the view model. The activity calls methods from the view model and observes with live data. But ee can also give feedback to the user.
Look at this snippet to see the light-weight thread in action:
So, as a detail, the button is disabled to avoid multiple clicks; and enabled when all is finished. But the important thing is, that everything happens in a coroutine! launch(UI) { BlockOfCode } is the way to work with coroutines in android activities. The user clicks the button and while the coroutine is running the user can make other actions. The view is still responsive.
ViewModel: Our first suspend method
The ViewModel delegates the responsibility to sync data to the repository. What changes here if we want to work with coroutines is the suspend keyword added to the method. This is necessary to compile, because suspend functions should be called from another suspend function or from a coroutine. And the syncPets method from the repository is suspended.
Without coroutines:
With coroutines:
ViewModel: flavoring the example by adding exception handling and feedback
We are going to create the default message, then ask for the syncPets in a way that we can manage a possible launched exception. To do that, we create an async coroutine
This seems weird, because we’re mixing synchronous and asynchronous logic, but it’s a way to work with exceptions thrown by this coroutine. This code will make sense if you read the following:
To understand the difference between launch { } and async { }: this.
To know what the meaning of await for the deferred is: this.
Repository: Synchronizing our data
The repository is in charge of getting the data, deleting our local pets and inserting the remote ones.
Without coroutines, we do this:
The first time, the live data has an empty list of pets. It’s not a huge deal because it works afterwards. The liveDataMerger, shared with the method responsible for getting and showing all pets, is updated with the list of pets obtained from Firestore. Then, the local pets are deleted and the remote ones are inserted.
With coroutines, we do this:
Suspended methods can work with async tasks, pretty much like if it were sync code. First, we wait for the pets. Then local pets are deleted and the remote ones are inserted. Isn’t it more clear?
Getting data from firestore
This method returns a live data with an empty list of pets, while the success listener is waiting for the data. When the success handler is executed, the method “set Pets From Snapshot Into Mutable List” does its job and the LiveData its magic.
Just ask Firebase for the pets and wait for the response. Then we return the list of pets created by the same method as in the previous example. I like it :).
Managing exceptions inside a coroutine
But wait, what exception? The exception is launched into the getAllPets method, with the await extension:
gist#d885ee1a28a904fcbeef0cb43713a07b
The `continuation` parameter is actually a `future`. Thanks to that we can add a handler for the offline case (where we throw an exception). In addition to that, we can use the `when` statement to avoid having a lot of nested ifs.
We are still in our early steps with Kotlin coroutines. And we look for forward to using it more extensively. So all your feedback is welcome.