'Android generic Gson.fromJson() conversion with coroutine and flow

I am making generic classes for hitting otp api.anybody can use otp section just have to pass request ,Response class and url and all will be done by this otp section. Please note : this response class can be of different type (for eg: MobileOtpResponse,EmailOtpResponse)

below is the generic OtpClient which takes any request type and returns particular passed ResponseType (for example : Request class passed is OtpRequest ,ResponseType class passed is OtpResponse)

 interface OtpClient {
  @POST
  suspend fun <Request : Any, ResponseType> sendOtp(@Url url: String,
   @Body request:@JvmSuppressWildcards Any): @JvmSuppressWildcards ResponseType
 }

OtpRequest

data class OtpRequest(@SerializedName("mobile_number") val mobileNumber: String,@SerializedName("app_version") val appVersion: String)

OtpResponse

data class OtpResponse(@SerializedName("status") val status: String = "",
                       @SerializedName("response") val response: OtpData? = null)
data class OtpData(
        @SerializedName("otp_status") val otpStatus: Boolean = false,
        @SerializedName("message") val message: String = "",
        @SerializedName("error") val error: Int? = null,
        @SerializedName("otp_length") val otpLength: Int? = null,
        @SerializedName("retry_left") val retryLeft: Int? = null,)

Now i create Repo to call this api this simply use flow and when the data fetch it emits the data


class OtpRepoImpl<out Client : OtpClient>(val client: Client) :OtpRepo  {
    override fun <Request:Any, ResponseType> sentOtpApi(url: String, request: Request): Flow<ResponseType> {

        return flow<ResponseType> {
            // exectute API call and map to UI object

            val otpResponse = client.sendOtp<Request, ResponseType>(url,request)
            emit(otpResponse)
        }.flowOn(Dispatchers.IO) // Use the IO thread for this Flow
    }


}

this repo is used in viewmodel class

 @ExperimentalCoroutinesApi
    fun <A : Class<ResponseType>, Request : Any, ResponseType : Any> sendOtp(a: Class<ResponseType>, request: Request, response: ResponseType, url: String) {
        viewModelScope.launch {
            repo.sentOtpApi<Request, ResponseType>(url, request = request)
                    .onStart { _uiState.value = OtpState.Loading(true) }
                    .catch { cause ->
                        _uiState.value = OtpState.Loading(false)
                        getResponseFromError<Class<ResponseType>,ResponseType>(cause, response) {
//                            emit(it)
                        }
                    }
                    .collect {
                        _uiState.value = OtpState.Loading(false)
                        _uiState.value = OtpState.Success(it)
                    }
        }
    }

as you can see above this sendOtp method is called from the view class and inside this method we use repo.sentOtpApi and pass generic request response type.I get data in catch block coz api is send error otp data in 400 HttpException so i created another method getResponseFromError to get error response it should parse the errorBody response and call this lambda block.

 private suspend fun <A : Class<*>, ResponseType : Any> getResponseFromError( cause: Throwable, rp: ResponseType, block: suspend (ResponseType) -> Unit) {

        if (cause is HttpException) {
            val response = cause.response()

            if (response?.code() == 400) {
                println("fetching error Response")
                val errorResponse = response.errorBody()?.charStream()
                val turnsType = object : TypeToken<ResponseType>() {}.type
                val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
               block(finalErrorResponse)
            } else {
                println("someOther exception")
            }
        } else
            _uiState.value = OtpState.Error(cause)
    }

so here i am facing the problem inside above method

    val turnsType = object : TypeToken<ResponseType>() {}.type
    val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
    block(finalErrorResponse)

This finalErrorResponse is returning LinkedTreeMap instead of ResponseType (in this case its OtpResponse) i have also tried using Class<*> type like this

  val turnsType = object : TypeToken<A>() {}.type
  val finalErrorResponse = Gson().fromJson<A>(errorResponse, turnsType)

but its not working.

calling of this sentOtp viewmodel func is like

   var classType = OtpResponse::class.java
   otpViewModel.sendOtp(a = classType, request = otpRequest, response = OtpResponse() , url = 
   "http://preprod-api.nykaa.com/user/otp/v2/send-wallet-otp")

[![value in finalErroResponse][1]][1] [1]: https://i.stack.imgur.com/Holui.png

required: finalErroResponse should be of OtpResponse type because that was passed in sentOtp func

Please help :)



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source