Converting Callbacks to Kotlin Coroutines (with RevenueCat SDK Example)

Converting Callbacks to Kotlin Coroutines (with RevenueCat SDK Example)


2 min read


Modern Android codebases rely heavily on the new features of the Kotlin language. Coroutines have been a central part of Kotlin's ecosystem for quite some time now.

Many projects use Coroutines in their Services, Data Sources, and Repositories to leverage the power of writing asynchronous code and the readability of synchronous code.

If you have a modern Android codebase that relies on Courotines to keep your code simple and easier to maintain, you might encounter a third-party SDK that uses callbacks for their APIs. One of these is RevenueCat Android SDK.

The problem? -> Coroutines and callbacks do not mix together.

To keep your code nice and clean, you will need to transform the callbacks to Courutines.

Coroutines & Callbacks

Kotlin gives us 2 ways to transform something into a Coroutine:

  1. suspendableCoroutine

  2. suspendCancellableCoroutine

Both APIs serve the same purpose. The only difference is that suspendCancellableCoroutine supports the cancellation of the task at hand.

Most callbacks come with the following methods:

  • a method for retrieving the result, eg. onSuccess(actionResult: ActionResult)

  • a method for catching an error eg. onError(error: Throwable)

  • (optional) a method for canceling

Implementation might look something like this:

SDK.performAction(object : ActionListener {
    override fun onSuccess(actionResult: ActionResult) {


    override fun onError(error: Throwable) {


Wrapping Callbacks to Coroutines

Wrapping generally consists of 2 steps:

  1. Create a suspend function which returns the object that you would get from the onSuccess callback method.

  2. Use suspendCoroutine as the return block with the following:

    • continuation.resume(result) for success.
    • continuation.resumeWithException(throwable) for the error.

I'll use RevenueCat getOfferings as an example.

suspend fun Purchases.getOfferings(): Offerings {
    return suspendCoroutine { continuation ->
        getOfferings(object : ReceiveOfferingsCallback {
            override fun onError(error: PurchasesError) {

            override fun onReceived(offerings: Offerings) {

ReceiveOfferingsCallback returns Offerings object, so our method should return the same. It also returns a PurchasesError object in case an error happens. Since this is not Throwable we need to create one.

open class PurchasesException(
    private val purchasesError: PurchasesError
) : Exception() {
    val code: PurchasesErrorCode
        get() = purchasesError.code

    override val message: String
        get() = purchasesError.message

    val underlyingErrorMessage: String?
        get() = purchasesError.underlyingErrorMessage

    override fun toString() = purchasesError.toString()


Congratulations, you have successfully wrapped your first callback into a Coroutine ๐ŸŽ‰.

Now you can use it like this:

try {
  val offerings = Purchases.getSharedInstance.getOfferings()
} catch (e: Exception) {
  Log.e("getOfferings#Error", e.message.orEmpty())

If you have enjoyed or found this helpful, make sure to follow me for more content like this!


If you want to use Coroutines with the RevenueCat SDK, I've prepared a wrapper for you. Check out RevenueKt.