How to convert custom callback to coroutine - android

I am using Stripe library which provides me with custom callback functionality.
I want a custom callback convert to Kotlin coroutine
Here is the code
override fun retrievePaymentIntent(clientSecret: String): Flow<Resource<PaymentIntent>> = flow{
emit(Resource.Loading())
Terminal.getInstance().retrievePaymentIntent(clientSecret,
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException) {}
override fun onSuccess(paymentIntent: PaymentIntent) {
emit(Resource.Success(paymentIntent))
}
})
}
The problem is I can't call emit function inside onSuccess/onFailure. The error shown in the picture.
Is it possible to change something here to make it work or how could I convert custom callback to coroutine?

You can use suspendCancellableCoroutine to model your callback-based one-shot request like so:
suspend fun retrievePaymentIntent(clientSecret: String): PaymentIntent =
suspendCancellableCoroutine { continuation ->
Terminal.getInstance().retrievePaymentIntent(clientSecret,
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException)
{
continuation.resumeWithException(e)
}
override fun onSuccess(paymentIntent: PaymentIntent)
{
continuation.resume(paymentIntent)
}
})
continuation.invokeOnCancellation { /*cancel the payment intent retrieval if possible*/ }
}

Related

Kotlin - How to wait for multiple API calls to finish and update UI?

I'm new to kotlin coroutines. I've been trying to run multiple API calls in parallel and then when all the calls are done update my UI and dismiss the loader, but with no success. This is my code
private fun getScoreForType() {
val job = CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
}
runBlocking {
job.join()
// do some ui work
dismissLoader()
}
}
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}
I've also tried using async and awaitAll but couldn't make it work either. The loader is always dismissed before all the calls are done. Any help on how I could make this work would be much appreciated
Use Flow and collectData it will works as LiveData.
For example:
val myIntFlow = MutableStateFlow(-1)
Try something like;
in ViewModelMethods
private fun getScoreForType() {
It goes first:
CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
// it means to change value of flow
myIntFlow.value = 1
}
// Now collect data in fragment to change UI
}
// in fragment like:
CoroutineScope(Dispatchers.Main).launch {
// flow will be triggered, on every changed value
viewModel.myIntFlow.collect {
viewModel.methodFromViewModelToChangeUI()
dissmisloader()
myIntFlow.value = -1
}
}
// try the same here as you wish
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}

Coroutine and Callback handler in kotlin

I am currently building an app using AWS SDK. One of the API is a sign in and is requiring, in addition to email and password, a Callback in order to get back the status of the request. The issue is that I am not able to send back the result.
This is my code:
override suspend fun signIn(email: String, password: String): Result<SignInResult> =
withContext(ioDispatcher) {
try {
api.signIn(email, password, object : Callback<SignInResult> {
override fun onResult(result: SignInResult?) {
Result.Success(result!!)
}
override fun onError(e: Exception?) {
Result.Error(e!!)
}
})
} catch (e: Exception) {
Result.Error(e)
}
}
The issue is that coroutine sign in is requiring a return of Result but I do not know what to return because I should only return when onResult, onError and when catching an exception.
Any idea how to make it works ?
Thanks
You can use suspendCoroutine or suspendCancellableCoroutine to work with callbacks:
override suspend fun signIn(email: String, password: String): Result<SignInResult> =
suspendCoroutine { continuation ->
try {
api.signIn(email, password, object : Callback<SignInResult> {
override fun onResult(result: SignInResult) {
// Resume coroutine with a value provided by the callback
continuation.resumeWith(Result.Success(result))
}
override fun onError(e: Exception) {
continuation.resumeWith(Result.Error(e))
}
})
} catch (e: Exception) {
continuation.resumeWith(Result.Error(e))
}
}
suspendCoroutine suspends coroutine in which it executed until we decide to continue by calling appropriate methods - Continuation.resume.... suspendCoroutine mainly used when we have some legacy code with callbacks.
There is also suspendCancellableCoroutine builder function, it behaves similar to suspendCoroutine with additional feature - provides an implementation of CancellableContinuation to the block.

How to emit data from an asycnhronous callback using Kotlin Flow?

I'm starting to learn Kotlin Flow and Coroutines but I do not know how to make the code below works. What am I doing wrong?
interface MessagesListener {
fun onNewMessageReceived(message: String)
}
fun messages(): Flow<String> = flow {
val messagesListener = object : MessagesListener {
override fun onNewMessageReceived(message: String) {
// The line below generates the error 'Suspension functions can be called only within coroutine body'
emit(message)
}
}
val messagesPublisher = MessagesPublisher(messagesListener)
messagesPublisher.connect()
}
I believe you should be able to use callbackFlow ....something like:
fun messages(): Flow<String> = callbackFlow {
val messagesListener = object : MessagesListener {
override fun onNewMessageReceived(message: String) {
trySend(message)
}
}
val messagesPublisher = MessagesPublisher(messagesListener)
messagesPublisher.connect()
}
What you are trying to achieve is not possible because emit is a suspend function.
However, you can use callbackFlow which is designed for such cases to convert Listeners/Callbacks into Coroutine's Flows.
fun messages() = callbackFlow<String> {
val messagesListener = object : MessagesListener {
override fun onNewMessageReceived(message: String) {
trySend(message)
}
}
val messagesPublisher = MessagesPublisher(messagesListener)
messagesPublisher.connect()
}

Generic grpc requests in kotlin

class API {
val nonBlockingStub: HealthcareAPIGrpc.HealthcareAPIStub //Generated Java GRPC Stub
suspend fun exampleRequest(params: Params) : ReturnedResult = suspendCancellableCoroutine { routine ->
val obs = object : StreamObserver<ReturnedResult> {
override fun onNext(value: ReturnedResult?) {
routine.resume(value!!)
}
override fun onError(t: Throwable?) {
routine.resumeWithException(t!!)
}
override fun onCompleted() {
}
}
nonBlockingStub.exampleRequest(params, obs)
}
}
So I'm working on an Kotlin android application, which has a grpc client generated in java. Recently I had to move all the API requests to use kotlin coroutines. For every request I have to copy-paste this exampleRequest function.
I am curious if it's possible to make a generic function which does this, takes some params and calls the underlying stub function
Ideally there should be a stub generator that generates the appropriate calls as suspend/Flow methods, but you can still abstract much of the conversion with a dedicated helper function:
fun <T> grpcFlow(
#BuilderInference block: suspend (StreamObserver<T>) -> Unit
): Flow<T> = callbackFlow {
// Use ClientCallStreamObserver to support cancellation
val observer = object : ClientCallStreamObserver<T>() {
override fun onNext(value: T) {
sendBlocking(value)
}
override fun onError(t: Throwable) {
cancel(CancellationException("GRPC Error", t))
}
override fun onCompleted() = channel.close()
}
block(observer)
awaitClose {
observer.cancel("flow cancellation", null)
}
}
Then your API simply becomes:
class API {
val nonBlockingStub: HealthcareAPIGrpc.HealthcareAPIStub
suspend fun exampleRequest(params: Params) = grpcFlow {
// #BuilderInference should ensure the correct type is used
nonBlockingStub.exampleRequest(params, it)
}.single() // Since we only expect a single response value.
// And for returns (stream ReturnedResult)
suspend fun exampleStreamingRequest(params: Params) = gcpcFlow {
nonBlockingStub.exampleStreamingRequest(params, it)
} // Flow<ReturnedResult>
}

Kotlin lambda function with arguments

I am currently developing an Android application that uses the Retrofit library for REST api usage.
For instance, I have the following code from MainActivity.kt :
fun userLogin(){
calls.userLogin() { updateUiComponents() }
}
fun updateUiComponents(){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
And I have in a separate file the definition of the Retrofit calls:
fun userLogin(postActionMethod: () -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod()
}
})
}
After the Retrofit call is implemented and it is successful, reaching the onResponse method, I would like to send the Response object as a parameter of the lambda function back to the MainAcativity.kt. From the MainActivity.kt, the lambda function would use this information to perform some specific task.
Is that a way of defining a lambda function like this, with arguments? If it is the case, how can I pass the lambda function as a parameter like done on the following line:
calls.userLogin(body) { updateUiComponents() }
Thank u!
I don't know if I get what your problem is but a lambda does not need to do not have any parameter. You can easily do something like
fun userLogin(postActionMethod: (Response<LoginResponse>?) -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod(response)
}
})
}
so you consume it with
fun userLogin(){
calls.userLogin() { updateUiComponents(it) }
}
fun updateUiComponents(response: Response<LoginResponse>?){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}

Categories

Resources