My final goal here is to have the following retrofit interface declared:
interface SomeService {
@GET("/path")
fun getSomethingFlow() : Flow<DataState<SomeApiDataClass>>
}
data class DataState<T>(val data : T, state: State)
// the real data class is a big bigger, but that's the idea
// state is an enum
The intention is that my CallAdapter will handle the Flow and the DataState and let Moshi handle the T.
I've been following mostly from here https://github.com/MohammadSianaki/Retrofit2-Flow-Call-Adapter/tree/master/FlowAdapter/src/main/java/me/sianaki/flowretrofitadapter
So I created a CallAdapter<T, Flow<DataState<T>>> with override fun adapt(call: Call<T>): Flow<DataState<T>> that does all the processing I want it to do.
I've created the CallAdapter.Factory() and all the API data classes have Moshi annotation @JsonClass(generateAdapter = true).
here is the CallAdapter.Factory:
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != Flow::class.java) {
return null
}
if (returnType !is ParameterizedType) {
return null
}
val flowType = getParameterUpperBound(0, returnType)
val rawFlowType = getRawType(flowType)
if (rawFlowType != DataState::class.java) {
return null
}
return MyCallAdapter(flowType)
}
and relevant CallAdapter code:
override fun adapt(call: Call<T>): Flow<DataState<T>> {
Log.d("TEST", "adapt $call")
// do all my logic here.
This method is never called!!!
return dataState
}
override fun responseType(): Type {
Log.d("TEST", "Retrofit is asking us the type")
return returnType
}
At the end I got my Retrofit building like:
val api = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(MyAdapterFactory())
.client(client)
.baseUrl("https://.... ")
.build()
.create(SomeService::class.java)
When I try to call the API I get the following crash:
java.lang.IllegalArgumentException: Unable to create converter for com.package.DataState<com.package.SomeServiceDataClass>
for method SomeService.getSomethingFlow
at retrofit2.Utils.methodError(Utils.java:54)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:126)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:85)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
.....
Caused by: java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.package.DataState Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapterFactory from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:97)
at com.squareup.moshi.Moshi.adapter(Moshi.java:145)
at com.squareup.moshi.Moshi.adapter(Moshi.java:105)
It seems that even thou I gave to Retrofit a CallAdapter from Call<T> to Flow<DataState<T>>, it still trying to use data converters (moshi) for parsing the DataState.
On debugging I found that the Factory passes all the checks and returns a new MyCallAdapter and that Retrofit calls my MyCallAdapter.responseType(). So it's mostly there.
The fun adapt(call: Call<T>): Flow<DataState<T>> is never called!
So the question is:
How do I tell retrofit to only use Moshi for T and leave the DataState to me?
