Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,121 +1,64 @@
package com.omega_r.base.remote

import com.squareup.moshi.Types.getRawType
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import com.omega_r.base.errors.ErrorHandler
import com.omega_r.libs.extensions.common.ifNull
import okhttp3.Request
import retrofit2.*
import java.lang.reflect.Type

/**
* A [CallAdapter.Factory] for use with Kotlin coroutines.
*
* Adding this class to [Retrofit] allows you to return [Deferred] from
* service methods.
*
* interface MyService {
* @GET("user/me")
* Deferred<User> getUser()
* }
*
* There are two configurations supported for the [Deferred] type parameter:
*
* * Direct body (e.g., `Deferred<User>`) returns the deserialized body for 2XX responses, throws
* [HttpException] errors for non-2XX responses, and throws [IOException][java.io.IOException] for
* network errors.
* * Response wrapped body (e.g., `Deferred<Response<User>>`) returns a [Response] object for all
* HTTP responses and throws [IOException][java.io.IOException] for network errors
*/
class CoroutineCallAdapterFactory (private val parent: Job? = null,
private val errorConverter: ((Throwable) -> Exception)? = null) : CallAdapter.Factory() {


override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
if (Deferred::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Deferred return type must be parameterized as Deferred<Foo> or Deferred<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)

val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
check(responseType is ParameterizedType) { "Response must be parameterized as Response<Foo> or Response<out Foo>" }
ResponseCallAdapter<Any>(getParameterUpperBound(0, responseType), parent, errorConverter)
} else {
BodyCallAdapter<Any>(responseType, parent, errorConverter)
}
class CoroutineCallAdapterFactory(private val errorConverter: ((Throwable) -> Exception)? = ErrorHandler()) : CallAdapter.Factory() {

override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
val callAdapter = retrofit.nextCallAdapter(this, returnType, annotations)
return CallAdapterWrapper(callAdapter)
}

private class BodyCallAdapter<T>(private val responseType: Type,
private val parent: Job? = null,
private val errorConverter: ((Throwable) -> Exception)? = null) : CallAdapter<T, Deferred<T?>> {
inner class CallAdapterWrapper<R, T>(private val adapter: CallAdapter<R, T>) : CallAdapter<R, T> {

override fun responseType() = responseType
override fun adapt(call: Call<R>): T = adapter.adapt(CallWrapper(call))

override fun adapt(call: Call<T>): Deferred<T?> {
val deferred = CompletableDeferred<T?>(parent)
override fun responseType(): Type = adapter.responseType()

deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
}

inner class CallWrapper<R>(private val delegate: Call<R>) : Call<R> {

override fun enqueue(callback: Callback<R>) {
delegate.enqueue(object : Callback<R> {

call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(errorConverter?.invoke(t) ?: t)
override fun onFailure(call: Call<R>, t: Throwable) {
callback.onFailure(call, errorConverter?.invoke(t) ?: t)
}

override fun onResponse(call: Call<T>, response: Response<T>) {
override fun onResponse(call: Call<R>, response: Response<R>) {
if (response.isSuccessful) {
deferred.complete(response.body())
callback.onResponse(call, response)
} else {
val httpException = HttpException(response)
deferred.completeExceptionally(errorConverter?.invoke(httpException) ?: httpException)
errorConverter?.invoke(HttpException(response))?.let {
callback.onFailure(call, it)
}.ifNull {
callback.onResponse(call, response)
}
}
}
})

return deferred
}
}

private class ResponseCallAdapter<T>(private val responseType: Type,
private val parent: Job? = null,
private val errorConverter: ((Throwable) -> Exception)? = null
) : CallAdapter<T, Deferred<Response<T>>> {
override fun isExecuted(): Boolean = delegate.isExecuted

override fun responseType() = responseType
override fun clone(): Call<R> = CallWrapper(delegate.clone())

override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>(parent)
override fun isCanceled(): Boolean = delegate.isCanceled

deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
override fun cancel() {
delegate.cancel()
}

call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(errorConverter?.invoke(t) ?: t)
}
override fun execute(): Response<R> = delegate.execute()

override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
override fun request(): Request = delegate.request()

return deferred
}
}
}

}