I am building an app which is using AWS mobile Client SDK but I would like to use coroutine but I have an issue as the SDK from AWS is using Callback.
override suspend fun login(username: String, password: String): ResultResponse<SignInResult> {
AWSMobileClient.getInstance()
.signIn(username, password, null, object : Callback<SignInResult> {
override fun onResult(signInResult: SignInResult) {
ResultResponse.Success(signInResult)
}
override fun onError(e: Exception?) {
if (e != null) {
ResultResponse.Error(e)
}
}
})
}
Any idea how to workaround this or do it properly ?
Regards
CodePudding user response:
Asynchronous callbacks can be converted into synchronous suspend functions using either suspendCoroutine()
or suspendCancellableCoroutine()
. Typically you would create a suspend extension function version of the corresponding asynchronous call so you can use it anywhere you would normally use the SDK call (rather than using suspendCoroutine
at each location, since it is somewhat clumsy).
suspendCancellableCoroutine
is used when the API call provides a way to cancel it early. If that's the case, you can make your suspend function support coroutine cancellation, so the API call is automatically cancelled if the associated coroutine is cancelled. Exactly what you want since on Android, lifecycleScope
and viewModelScope
automatically cancel their coroutines when their associated lifecycles are over.
I don't know what version of AWS SDK you're using, but here's approximately how you what your suspend function would look like:
suspend fun AWSMobileClient.signIn(userName: String, password: String, someParam: Any?): SignInResult = suspendCoroutine { continuation ->
signIn(userName, password, someParam, object : Callback<SignInResult> {
override fun onResult(signInResult: SignInResult) {
continuation.resume(signInResult)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
I don't know the type of that third parameter so I just put Any?
.
And now your existing function would become:
override suspend fun login(username: String, password: String): ResultResponse<SignInResult> {
return try {
val signInResult = AWSMobileClient.getInstance().signIn(username, password, null)
ResultResponse.Success(signInResult)
} catch(e: Exception) {
ResultResponse.Error(e)
}
}
I'm not sure if ResultResponse
is your own class. In case it is, be aware the Kotlin standard library already contains a Result class, which would make this function simpler with the use of runCatching
:
override suspend fun login(username: String, password: String): Result<SignInResult> {
return runCatching {
AWSMobileClient.getInstance().signIn(username, password, null)
}
}
CodePudding user response:
The usual way to convert callback-based APIs to suspending functions is to use suspendCoroutine or suspendCancellableCoroutine (if the callback API is cancellable).
In your case, it will look something like this:
override suspend fun login(username: String, password: String): SignInResult {
return suspendCoroutine { cont ->
AWSMobileClient.getInstance()
.signIn(username, password, null, object : Callback<SignInResult> {
override fun onResult(signInResult: SignInResult) {
cont.resume(signInResult)
}
override fun onError(e: Exception?) {
if (e != null) {
cont.resumeWithException(e)
}
}
})
}
}
This suspends the current coroutine until the callback is called with the result or an error. Note that the login()
method will throw an exception if onError
is called (which is usually what you want when using suspend functions).