Kotlin coroutines @ Vimeo: A case study, part 1

Mohit Sarveiya
Vimeo Engineering Blog
4 min readMar 26, 2019

--

For the past couple of months, I’ve been battle-testing coroutines in simple and complex uses cases in our app and our open-source SDK. These use cases are bridging our callback-based SDK to coroutines, uploading videos, polling in our player, and interacting with our internal RxJava-based libraries.

In this blog series, I want to share with you how I went about integrating coroutines with these use cases and talk about some of the challenges I encountered. Part 1 of the series, which you’re reading right now, explores how to use coroutines in the simplest use case of making an API request.

The simplest page in the Vimeo app is the privacy policy page. We make an API request, get HTML data, and display it in a TextView widget. The page has a pull to refresh and an error state. We use this feature as a playground to illustrate new patterns and library usage. It’s our go-to feature to see how we use the Model View Presenter, or MVP, architecture. In keeping with the theme of this series, I’ll show you how we converted this feature to use coroutines with MVP. Let’s dive into the details.

Bridging callbacks to coroutines

First, let’s look at how we can use coroutines in the data layer. We interact with our API through our open-source SDK — Vimeo Networking Java. The SDK provides a VimeoClient class that contains methods to get data from the API. In our use case, we want to use the getDocument method to get the privacy policy based on a URI.

Here’s an example of how to make the request using a callback:

VimeoCallback is our custom Retrofit callback that returns a VimeoError. So, how do we perform this request using coroutines without changing the SDK? We need a way to bridge the callback-based SDK to coroutines.

The coroutines library provides a higher-order function called suspendCancellableCoroutine, which enables us to do this:

This method accepts a lambda and provides a Continuation object as a parameter to specify when to resume the coroutine.

Here’s an example of using suspendCancellableCoroutineto get a document. There are three steps to follow to bridge the getDocument method to a suspending function:

  1. Create a suspend function whose return type is the desired result from the request, like this:

I’ve created the suspend function getDocument by marking it with the suspend keyword. This keyword gives this function the power to suspend itself until a response is returned by the API. The response is represented by a sealed class Result. This class encapsulates the data and any error that may have occurred.

2. Invoke the suspendCancellableCoroutinemethod to wrap the callback, like this:

We’re invoking the suspendCancellableCoroutinemethod and passing into it a lambda. Like before, the method expects a function of type (CancellableContinuation<T>) -> Unitto be passed in. The contparam above is the Continuation we’ll use to specify when to resume and cancel the coroutine.

3. Specify resuming and cancelling the coroutine, like this:

In the success and error callbacks above, we’ll resume the coroutine with a specific type in the Result sealed class. On cancellation, we’ll cancel the Retrofit Call object.

This strategy for bridging callbacks to coroutines works well. However, it can become cumbersome for a developer always to have to wrap a method in suspendCancellableCoroutine. Is there a way to build a factory that can take any method in the SDK and convert it to suspending method?

A scalable approach

Let’s look at the getDocument method in VimeoClient and observe its type:

This function has the type (A, VimeoCallback<T>) -> Call<T>where A represents the URI. It returns the Call object from Retrofit. There are other methods provided by VimeoClient that have more than two arguments. Their type is (A,B,VimeoCallback<T>) -> Call<T>. However, the last argument in these methods is always VimeoCallback. This information is useful in creating a generic method to convert any method of type (A, VimeoCallback<T>) -> Call<T> to a suspending function. Our goal is to build a factory that maps a function of type (A, VimeoCallback<T>) -> Call<T>to suspend (A) -> Result<T>.

Here’s our function to accomplish that goal:

The convertToSuspendFunction function takes a function of type
fn:(A, VimeoCallback<T>) -> Call<T>. This represents any function in the SDK with two parameters. Inside the suspendCancellableCoroutinemethod, we’re invoking the fn method and giving it an instance of VimeoCallback. As we saw earlier, we specify when the coroutine will resume when the callback gives us a response.

Let’s look at the usage of convertToSuspendFunction:

We passed into convertToSuspendFunction a reference of the getDocument method. We got a suspending function back and used it to invoke a URI.

Setting up our repository

Now we have a generic way to convert any method in the SDK to a suspending function. We could create a module in the Vimeo Networking Java SDK to provide a suspending function. We’ll explore this approach later. Assume we weren’t able to modify an external SDK. Where do we place the conversion logic in our app, and how do we use it as we’re building this feature in a Model View Presenter architecture?

In our app, we have a repository that enables you to interact with the SDK. It wraps VimeoClient, and handles caching and network connectivity. The conversion logic would live in the repository layer. I created the following factory, which contains the logic to do the conversion and give me a suspending function:

This is the same code that you saw before, only now it’s behind an interface. I inject this factory into my repository so that it has the ability to convert any suspending function, like this:

With this approach, I’ve created a bridge from callbacks to coroutines in the app without modifying the SDK.

In the next blog post of this series, we’ll look at how to use this repository with scopes and dispatchers in an MVP setup.

--

--