Introduction
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:
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:
Create a
suspend
function which returns the object that you would get from theonSuccess
callback method.-
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) {
continuation.resumeWithException(PurchasesException(error))
}
override fun onReceived(offerings: Offerings) {
continuation.resume(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()
}
Conclusion
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!
Reposted from my blog.
Bonus
If you want to use Coroutines with the RevenueCat SDK, I've prepared a wrapper for you. Check out RevenueKt.
Top comments (0)