I need to create a retrofit call adapter which can handle such network calls:
@GET("user")
suspend fun getUser(): MyResponseWrapper<User>
I want it to work with Kotlin Coroutines without using Deferred
. I have already have a successful implementation using Deferred
, which can handle methods such as:
@GET("user")
fun getUser(): Deferred<MyResponseWrapper<User>>
But I want the ability make the function a suspending function and remove the Deferred
wrapper.
With suspending functions, Retrofit works as if there is a Call
wrapper around the return type, so suspend fun getUser(): User
is treated as fun getUser(): Call<User>
My Implementation
I have tried to create a call adapter which tries to handle this. Here is my implementation so far:
Factory
class MyWrapperAdapterFactory : CallAdapter.Factory() {
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
val rawType = getRawType(returnType)
if (rawType == Call::class.java) {
returnType as? ParameterizedType
?: throw IllegalStateException("$returnType must be parameterized")
val containerType = getParameterUpperBound(0, returnType)
if (getRawType(containerType) != MyWrapper::class.java) {
return null
}
containerType as? ParameterizedType
?: throw IllegalStateException("MyWrapper must be parameterized")
val successBodyType = getParameterUpperBound(0, containerType)
val errorBodyType = getParameterUpperBound(1, containerType)
val errorBodyConverter = retrofit.nextResponseBodyConverter<Any>(
null,
errorBodyType,
annotations
)
return MyWrapperAdapter<Any, Any>(successBodyType, errorBodyConverter)
}
return null
}
Adapter
class MyWrapperAdapter<T : Any>(
private val successBodyType: Type
) : CallAdapter<T, MyWrapper<T>> {
override fun adapt(call: Call<T>): MyWrapper<T> {
return try {
call.execute().toMyWrapper<T>()
} catch (e: IOException) {
e.toNetworkErrorWrapper()
}
}
override fun responseType(): Type = successBodyType
}
runBlocking {
val user: MyWrapper<User> = service.getUser()
}
Everything works as expected using this implementation, but just before the result of the network call is delivered to the user
variable, I get the following error:
java.lang.ClassCastException: com.myproject.MyWrapper cannot be cast to retrofit2.Call
at retrofit2.HttpServiceMethod$SuspendForBody.adapt(HttpServiceMethod.java:185)
at retrofit2.HttpServiceMethod.invoke(HttpServiceMethod.java:132)
at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
at com.sun.proxy.$Proxy6.getText(Unknown Source)
...
From Retrofit's source, here is the piece of code at HttpServiceMethod.java:185
:
@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call); // ERROR OCCURS HERE
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
}
I'm not sure how to handle this error. Is there a way to fix?
See Question&Answers more detail:
os