Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A variation of Jake Wharton's deferred call adapter that better supports `Unit` and `Void` return types
package co.zsmb.example
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class DeferredCallAdapterFactory private constructor() : CallAdapter.Factory() {
companion object {
@JvmStatic
@JvmName("create")
operator fun invoke() = DeferredCallAdapterFactory()
private val UNIT_TYPES = arrayOf(Unit::class.java, Void::class.java)
}
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)
if (returnType.actualTypeArguments.isNotEmpty() && returnType.actualTypeArguments[0] in UNIT_TYPES) {
return UnitCallAdapter(responseType)
}
val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Response<Foo> or Response<out Foo>")
}
ResponseCallAdapter<Any>(getParameterUpperBound(0, responseType))
} else {
BodyCallAdapter<Any>(responseType)
}
}
private class BodyCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<T>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class UnitCallAdapter(private val responseType: Type) : CallAdapter<Unit, Deferred<Any?>> {
override fun responseType() = responseType
override fun adapt(call: Call<Unit>): Deferred<Any?> {
val deferred = CompletableDeferred<Any?>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<Unit> {
override fun onFailure(call: Call<Unit>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
if (response.isSuccessful) {
deferred.complete(Unit)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class ResponseCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<Response<T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
return deferred
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment