I have a working application, but I would like to improve one point. The method ("private fun save"), which is responsible for saving the information I need, I would like to make asynchronous.
But the problem is that when I change it to - "private suspend fun save", I have to make suspend and override fun intercept method. But since it is override, I get an error:
Conflicting overloads: public open suspend fun intercept(chain:
Interceptor.Chain): Response defined in
com.pocketscout.network.PocketScoutInterceptor, public abstract fun
intercept(chain: Interceptor.Chain) : Response defined in
okhttp3.Interceptor.
Is this problem somehow solved?
class PocketScoutInterceptor(
private val appContainer: PocketScoutContainer,
) : okhttp3.Interceptor {
#Throws(IOException::class)
override fun intercept(chain: okhttp3.Interceptor.Chain): okhttp3.Response {
val packet = buildPacket(timestamp, duration, request, response, description)
save(packet)
return response ?: okhttp3.Response.Builder().build()
}
At this moment OkHttp3 doesn't support suspend feature for interceptors. You can wrap save method
private suspend fun save(packet: Packet) { // Make suspend
...
}
runBlocking { save(packet) } // Call inside intercept
or all inside it:
private fun save(packet: Packet) = runBlocking {
...
}
Use this code at your own risk.
Related
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*/ }
}
Recently, I applied Coroutines into my project everything seems to be fine but today I meet a problem [Upload file/image into server using Coroutine + Retrofit][1]
It seems that there is no solution for upload file using coroutine + Retrofit, so we must use callback for retrofit.
//Api interface
interface UploadFileApiKotlin {
#Multipart
#POST("/uploadFileApi")
fun uploadFiles(
#Part listUri: List<MultipartBody.Part>
): Call<Response<List<FileResponse>?>>
}
//ViewModel class
serviceScope.launch {
//Insert into db
repository.insertNewExtAct()
//Send data into server.
val call = RequestHelper.getUpLoadFilesKotlinRequest().uploadFiles(partList)
call.enqueue(object : Callback<Response<List<FileResponse>?>> {
override fun onResponse(
call: Call<Response<List<FileResponse>?>>,
response: Response<Response<List<FileResponse>?>>
) {
TODO("Not yet implemented")
}
override fun onFailure(
call: Call<Response<List<FileResponse>?>>,
t: Throwable
) {
TODO("Not yet implemented")
}
})
//Wait for response from server and do logic
}
My question is: How can I suspend coroutines's execution to wait for retrofit's callback ?
Thank you bro.
[1]: Upload file/image into server using Coroutine + Retrofit
You can use suspendCoroutine or suspendCancellableCoroutine to work with callbacks (to convert a callback to a suspend function):
serviceScope.launch {
//Insert into db
repository.insertNewExtAct()
//Send data into server.
uploadFile()
//Wait for response from server and do logic
}
suspend fun uploadFile() = suspendCoroutine<Response<List<FileResponse>?>> { continuation ->
val call = RequestHelper.getUpLoadFilesKotlinRequest().uploadFiles(partList)
call.enqueue(object : Callback<Response<List<FileResponse>?>> {
override fun onResponse(
call: Call<Response<List<FileResponse>?>>,
response: Response<Response<List<FileResponse>?>>
) {
// Resume coroutine with a value provided by the callback
continuation.resumeWith(response.data)
}
override fun onFailure(
call: Call<Response<List<FileResponse>?>>,
t: Throwable
) {
continuation.resumeWithException(t)
}
})
}
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.
My Android Application is base on a TCP protocol.
When I'm initializing a connection to the server, I'm sending a special bytes message and have to wait the response of the server.
In all the repositories example I have seen the repository have always methods to call the source of information with a return (from Android Developers) :
class UserRepository {
private val webservice: Webservice = TODO()
// ...
fun getUser(userId: String): LiveData<User> {
// This isn't an optimal implementation. We'll fix it later.
val data = MutableLiveData<User>()
webservice.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
TODO()
}
})
return data
}
}
The function getUser return a data of LiveData.
In my app the method Login return nothing because I wait for the server to send bytes with a special code to know that is responding to my login request.
Is there a way to implement this pattern with TCP protocols like that ?
Thanks
They honestly should have just written the following code:
class UserRepository {
private val webService: WebService = TODO()
// ...
fun getUser(userId: String, successCallback: (User) -> Unit) {
webService.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
successCallback(response.body())
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
}
})
}
}
LiveData is not meant to represent one-off callbacks.
Then call it as
userRepository.getUser(userId) { user ->
// do whatever
}
For a proper reactive implementation, refer to https://stackoverflow.com/a/59109512/2413303
So, I'm rewriting my app's code te be "clean" (separation of layers, following MVVM pattern recommended by Android Team)
Here I've got my simple Retrofit interface to communicate with my API
interface Api {
#GET("comments")
suspend fun getPlaceComments(#Query("placeId") placeId: String): Response<List<CommentResponse>>
#POST("comments")
suspend fun addPlaceComment(#Header("placeId") placeId: String, #Header("text") text: String): Response<Unit>
#DELETE("comments")
suspend fun deletePlaceComment(#Header("placeId") placeId: String): Response<Unit>
}
Just a simple CRUD.
Now, one layer up, I've got my SocialRepository. To avoid code repetition, I created generic method callSafely that takes a suspending API function and a placeId as its parameters.
class SocialRepository {
private val client: Api = ApiClient.webservice
private suspend fun <T> callSafely(
apiMethod: suspend (placeId: String) -> Response<T>,
placeId: String,
): T? {
Log.d(TAG, "$apiMethod called safely")
var response: Response<T>? = null
try {
response = apiMethod(placeId)
} catch (e: Exception) {
e.printStackTrace()
}
if (response?.isSuccessful != true) {
Log.w(TAG, "response.isSuccessful isn't true.")
}
return response?.body()
}
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(client::getPlaceComments, placeId)
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(client::deletePlaceComment, placeId)
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(client::addPlaceComment, placeId, text) // HERE LIES THE PROBLEM
// I can't pass additional data because the method signature won't match with what's defined in callSafely()
}
}
Now, it works pretty well, of course I've got also my Activity and its ViewModel and the ViewModel calls a method in the repository etc. It doesn't matter.
What matters is that adding a place comment requires additional data, like, the actual text of the comment. Getting and deleting comments require only placeId, whereas when adding a comment, its content, its text is also required.
I've read that passing vararg functions is impossible in Kotlin. I also wouldn't like to clutter all the API methods with something like a List of params that will most of the time be empty and will just create confusion.
I can go the easy way and just copy the code of callSafely to addPlaceComment and alter it, but that's not what I'm looking for. I know how to solve the problem, but I don't know how to do it the clean way. In the future I might add some more endpoints requiring additional data (except placeId) and the problem will show up again.
What would you do in this situation? How to write it "the correct way"?
I'm not even sure how to properly express what I'm looking for, that's why this post's so rambling. Sorry for that in advance. I really hope you can help me.
The "clean way" is very broad concept. Everything depends on your needs and there is no "The one good way of doing things".
In your particular case you have several options:
1) Typealiases
typealias ApiCall1<P, R> = suspend (P) -> Response<R>
typealias ApiCall2<P1, P2, R> = suspend (P1, P2) -> Response<R>
fun <P> callSafely(param: P, call: ApiCall1<P, YourResult>): YourResult
fun <P1, P2> callSafely(param1: P1, param2: P2, call: ApiCall2<P1, P2, YourResult>): YourResult
2) Varargs
fun callSafely(vararg params: String, call: suspend (arr: Array<String>) -> YourResult {
...
call(*params)
...
}
3) Lambdas (preferable for your situation)
No-one is forcing you to use method references. Use lambdas when you need it. But place the lambda as the last parameter for "cleaner" code.
private suspend fun <T> callSafely(
placeId: String,
apiMethod: suspend (placeId: String) -> Response<T>
): T?
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(placeId, client::getPlaceComments)
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(placeId, client::deletePlaceComment)
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(placeId) { id -> client.addPlaceComment(id, text) }
}
Try this:
class SocialRepository {
private val client: Api = ApiClient.webservice
private suspend fun <T> callSafely(
apiMethod: suspend (placeId: String) -> Response<T>,
vararg stringParams: String,
): T? {
Log.d(TAG, "$apiMethod called safely")
var response: Response<T>? = null
try {
response = apiMethod(stringParams[0])
} catch (e: Exception) {
e.printStackTrace()
}
if (response?.isSuccessful != true) {
Log.w(TAG, "response.isSuccessful isn't true.")
}
return response?.body()
}
suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
return callSafely(apiMethod= client::getPlaceComments, stringParams=*arrayOf(placeId))
}
suspend fun deletePlaceComment(placeId: String): Unit? {
return callSafely(apiMethod=client::deletePlaceComment, stringParams=*arrayOf(placeId))
}
suspend fun addPlaceComment(placeId: String, text: String): Unit? {
return callSafely(apiMethod = client::addPlaceComment,stringParams= *arrayOf(placeId,text))
}
}
I want my NewsListSubscriber to inherit from an RxJava Subscriber which use a generic type but I get a "Type mismatch" error when I call the UseCase execute method. I read many times the generics page from the Kotlin documentation but I can't find the solution.
Here is my UseCase:
abstract class UseCase(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<Any>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<out Any>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
And here is how I call it:
override fun loadNewsList() {
getNewsListInteractor.execute(NewsListSubscriber())
}
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel>>() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
The error is
"Type mismatch. Required: rx.Subscriber. Found: Presenters.NewsListPresenter.NewsListSubscriber"
in the "execute(NewsListSubscriber())" line. I tried playing with the "in" and "out" keywords but I still have the same error.
There is actually a better way to solve this problem. I ran into the same issue and a type cast inside every derived subscriber class was not an option.
Just update the abstract UseCase class with an generic type parameter.
abstract class UseCase<T>(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<T>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<T>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
When you declare your derived UseCase classes, use your concrete type for the generic parameter when calling the super class.
class ConcreteUseCase(val threadExecutor: IThreadExecutor,
val postExecutionThread: IPostExecutionThread)
: UseCase<ConcreteType>(threadExecutor, postExecutionThread)
Doing so, you can use typed Subscribers in your execute call.
getNewsListInteractor.execute(NewsListSubscriber())
...
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
I found the solution that is pretty simple actually: my NewsListSubscriber class has to extends from rx.Subscriber<Any> instead of rx.Subscriber<MyWantedClass>. It means I need to cast the received objects to the wanted type.
private inner class NewsListSubscriber : DefaultSubscriber<Any>() {
override fun onCompleted() {}
override fun onError(e: Throwable) {}
override fun onNext(t: Any?) {
val newsList = t as List<News>
...
}
}
In Java the cast is done in background but in Kotlin we need to do it ourself.
I also removed all "in" or "out" keywords in my UseCase class.