1

I have a sealed class for state handling of my Retrofit responses. It's members take a generic type. I would like to get Retrofit to be able to return the proper object, but I am stuck at this error: Unable to create converter for com.my.app.DataResult<?> - Cannot serialize abstract class com.my.app.DataResult

This is my DataResult class:

sealed class DataResult<out T> {

    data class Success<out T>(val data: T?) : DataResult<T>()

    data class Error<out T>(val code: Int? = null, val error: Exception? = null) : DataResult<T>()
    object NetworkError : DataResult<Nothing>()

    fun isSuccess() = this is Success<*>
    fun isError() = this is Error<*>
    fun data() = if (isSuccess()) (this as Success<T>).data else null
}

fun successResult() = DataResult.Success(null)
fun <T> successResult(data: T?) = DataResult.Success(data)
fun errorResult() = DataResult.Error<Nothing>(null)

This is the rest of my current implementation:

class NetworkClient(private val httpClient: HttpClient) {

    private val baseUrl: String = "some url"

    private val retrofit = Retrofit.Builder()
        .baseUrl(mockend)
        .addCallAdapterFactory(MyCallAdapterFactory())
        .addConverterFactory(MoshiConverterFactory.create())
        .client(httpClient.get())
        .build()

    private val apiService: ApiService = retrofit.create(StaApiService::class.java)
    suspend fun <T> sendGet(endPoint: EndPoint, input: String): DataResult<T> {
        val result = apiService.sendGetRequest<T>(endPoint.stringValue, queryMapOf(Pair("query", input)))

        when (result) {
            // do stuff here?
        }
        return result
    }
}

interface ApiService {

    @GET
    suspend fun <T> sendGetRequest(
        @Url url: String,
        @QueryMap parameters: Map<String, String>): DataResult<T>

    @GET
    suspend fun <T> sendGetListRequest(
        @Url url: String,
        @QueryMap parameters: Map<String, String>): DataResult<List<T>>
}
abstract class CallDelegate<TIn, TOut>(
    protected val proxy: Call<TIn>
) : Call<TOut> {
    override fun execute(): Response<TOut> = throw NotImplementedError()
    final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
    final override fun clone(): Call<TOut> = cloneImpl()
    override fun cancel() = proxy.cancel()
    override fun request(): Request = proxy.request()
    override fun isExecuted() = proxy.isExecuted
    override fun isCanceled() = proxy.isCanceled
    abstract fun enqueueImpl(callback: Callback<TOut>)
    abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, DataResult<T>>(proxy) {
    override fun enqueueImpl(callback: Callback<DataResult<T>>) = proxy.enqueue(object : Callback<T> {
        override fun onResponse(call: Call<T>, response: Response<T>) {
            val code = response.code()
            val result: DataResult<T> = if (code in 200 until 300) {
                val body = response.body()
                DataResult.Success(body)
            } else {
                DataResult.Error(code)
            }

            callback.onResponse(this@ResultCall, Response.success(result))
        }

        override fun onFailure(call: Call<T>, t: Throwable) {
            val result: DataResult<Nothing> = if (t is IOException) {
                DataResult.NetworkError
            } else {
                DataResult.Error(null)
            }

            callback.onResponse(this@ResultCall, Response.success(result))
        }
    })

    override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
    private val type: Type
) : CallAdapter<Type, Call<DataResult<Type>>> {
    override fun responseType() = type
    override fun adapt(call: Call<Type>): Call<DataResult<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ) = when (getRawType(returnType)) {
        Call::class.java -> {
            val callType = getParameterUpperBound(0, returnType as ParameterizedType)
            when (getRawType(callType)) {
                Result::class.java -> {
                    val resultType = getParameterUpperBound(0, callType as ParameterizedType)
                    ResultAdapter(resultType)
                }
                else -> null
            }
        }
        else -> null
    }
}

The above code is largely inspired by this answer to another question, but I'm trying to add Generics to the mix, so I don't have to put every request into the interface by hand. Is it possible or not? I have tried for hours, also tried to build an adapter for the sealed class but failed. Has someone a good resource how this can be done?

As you can also see in the code I'd like to also be able to receive lists. Any tips here are much appreciated too.

michpohl
  • 852
  • 8
  • 30

0 Answers0