When working with multiple libraries, you may find yourself dealing with APIs that use callbacks. Converting these to use Kotlin Flows can help simplify your code.
To illustrate this, let’s look at the following function that uses callbacks :
fun getCustomerInfo(
onError: (PurchasesError) -> Unit,
onCustomerInfo: (CustomerInfo) -> Unit
) {
Purchases.sharedInstance.getCustomerInfoWith(
onError = { onError(it) },
onSuccess = { onCustomerInfo(it) }
)
}
We can simplify this by introducing suspend coroutines, but we still have callbacks:
fun getCustomerInfo(
onError: (PurchasesError) -> Unit,
onCustomerInfo: (CustomerInfo) -> Unit
) {
Purchases.sharedInstance.getCustomerInfoWith(
onError,
onCustomerInfo
)
}
However, we can now remove the callbacks entirely and use suspendCoroutine instead:
suspend fun getCustomerInfo(onError: (PurchasesError) -> Unit): CustomerInfo {
return suspendCoroutine { continuation ->
Purchases.sharedInstance.getCustomerInfoWith(
onError = onError,
onSuccess = { customerInfo ->
continuation.resume(customerInfo)
}
)
}
}
This allows us to make our function suspend, so that the caller waits until continuation.resume is called.
Best Practice
To further improve your code, it’s best to use the following version of the function:
suspend fun getCustomerInfo(): CustomerInfo {
return suspendCoroutine { continuation ->
Purchases.sharedInstance.getCustomerInfoWith(
onError = { error ->
throw PurchaseError()
},
onSuccess = { customerInfo ->
continuation.resume(customerInfo)
}
)
}
}
With the updated suspend function, the caller can now use the simplified code below to get the customer information:
val customerInfo = try {
getCustomerInfo()
} catch (e: PurchasesError) {
}
Converting to Flows
Learn how to convert callback-based APIs to Kotlin Flows when working with multiple libraries. Callback-based APIs can be challenging to work with, and it’s essential to know how to optimize them for Kotlin. Here’s an example of a callback-based API that is not Kotlin-friendly:
fun getUserEvents(
onSuccess: (UserEvents) -> Unit,
onError: (Error) -> Unit
) {
FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
.addSnapshotListener { snapshot, e ->
if (e != null) {
onError(e)
}
if (snapshot == null) {
return@addSnapshotListener
}
onSuccess(snapshot.getEvents())
}
}
Here’s how to change it to support flow response:
fun getUserEvents(): Flow<UserEvents> = callbackFlow {
var eventsCollection: CollectionReference? = null
try {
eventsCollection = FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
} catch (e: Throwable) {
close(e)
}
val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
if (snapshot == null) {
return@addSnapshotListener
}
try {
offer(snapshot.getEvents())
} catch (e: Throwable) {
}
}
awaitClose {
subscription?.remove()
}
}
Converting callback-based APIs to Kotlin Flows can make them easier to work with and optimize for Kotlin. Follow the above code snippets to learn how to convert them efficiently.
If you want to learn more about Kotlin Multiplatform and Compose Multiplatform find our best work here.