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.
Bridging callbacks to coroutines
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:
- 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
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:
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
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.