Search code examples
androidkotlingsonretrofit2

Retrofit wrap response in a generic class internally


I have a sealed class like below,

sealed class ApiResult<T>(
    val data: T? = null,
    val errors: List<Error>? = null
) {
    class Success<T>(data: T?) : ApiResult<T>(data)
    class Failure<T>(
        errors: List<Error>,
        data: T? = null
    ) : ApiResult<T>(data, errors)
}

And this is my Retrofit API interface,

interface Api {
    @POST("login")
    suspend fun login(@Body loginRequestDto: LoginRequestDto): ApiResult<LoginResponseDto>
}

What I want to achieve is, internally wrap the LoginResponseDto in BaseResponseDto which has the success, error, and data fields. Then put them in the ApiResult class.

data class BaseResponseDto<T>(
    val success: Boolean,
    val errors: List<Int>,
    val data: T?
)

In this case, LoginResponseDto is my data class. So, Retrofit should internally wrap the LoginResponseDto in BaseResponseDto then I will create the ApiResult response in my custom call adapter. How can I tell Retrofit to internally wrap this? I don't want to include the BaseResponseDto in the API interface every time.


Solution

  • After spending the whole day, I could achieve it. I had to create a custom converter. Here it is,

    class ApiConverter private constructor(
        private val gson: Gson
    ) : Converter.Factory() {
        override fun responseBodyConverter(
            type: Type,
            annotations: Array<out Annotation>,
            retrofit: Retrofit
        ): Converter<ResponseBody, *> {
            val baseResponseType = TypeToken.get(BaseResponseDto::class.java).type
    
            val finalType = TypeToken.getParameterized(baseResponseType, type)
    
            return Converter<ResponseBody, Any> { value ->
                val baseResponse: BaseResponseDto<*> =
                    gson.fromJson(value.charStream(), finalType.type)
                baseResponse
            }
        }
    
        companion object {
            fun create(gson: Gson) = ApiConverter(gson)
        }
    }
    

    Now I don't have to specify the BaseResponseDto every time in the API interface for retrofit.