Sevban Bayır
Sevban Bayır

Reputation: 736

Unable to invoke no-args constructor for class ConversionRates

I have a Currency Converter Android app. I 'm using retrofit for getting rates from API but as far as I'm concerned my app can't get data from api and it returns that exception

Unable to invoke no-args constructor for class package name.ConversionRates. Registering an InstanceCreator with Gson for this type may fix this problem.

ConversionRates data class:

data class ConversionRates(
val AED: Double,
val AFN: Double,
val ALL: Double,
val AMD: Double,
val ANG: Double,
val AOA: Double,
val ARS: Double,
val AUD: Double,
val AWG: Double,
val AZN: Double,
val BAM: Double,
val BBD: Double,
val BDT: Double,
val BGN: Double,
val BHD: Double,
val BIF: Double,
val BMD: Double,
val BND: Double,
val BOB: Double,
val BRL: Double,
val BSD: Double,
val BTN: Double,
val BWP: Double,)

viewModel:

@HiltViewModelclass MainViewModel @Inject constructor(
private val repository: MainRepository,
private val dispatchers: DispatcherProvider) : ViewModel(){

sealed class CurrencyEvent {
    class Success(val resultText: String): CurrencyEvent()
    class Failure(val errorText: String): CurrencyEvent()
    object Loading: CurrencyEvent()
    object Empty: CurrencyEvent() }

private val _conversion= MutableStateFlow<CurrencyEvent>(CurrencyEvent.Empty)
val conversion: StateFlow<CurrencyEvent> = _conversion

fun convert(amountStr: String,
    fromCurrency: String,
    toCurrency: String ){

    val fromAmount= amountStr.toFloatOrNull()
    if( fromAmount== null){
        _conversion.value=CurrencyEvent.Failure("Not a valid amount")
        return
    }
    viewModelScope.launch (dispatchers.io){
        _conversion.value= CurrencyEvent.Loading
        when( val ratesResponse= repository.getRates(fromCurrency)) {
            is Resource.Error -> _conversion.value= CurrencyEvent.Failure(ratesResponse.message!!)
            is Resource.Success -> {
                val rates = ratesResponse.data!!.conversion_rates
                val rate = getRateForCurrency(toCurrency, rates)
                if (rate==null){
                    _conversion.value= CurrencyEvent.Failure("Unexpected Error")
                } else {
                    //val convertedCurrency = round(fromAmount * rate * 100)
                    _conversion.value= CurrencyEvent.Success(
                        "$fromAmount $fromCurrency = $toCurrency"
                    )
                }
            }
        }
    }
}
private fun getRateForCurrency(currency: String, rates: ConversionRates) = when (currency) {
    "CAD" -> rates.CAD
    "HKD" -> rates.HKD
    "ISK" -> rates.ISK
    "EUR" -> rates.EUR
    "PHP" -> rates.PHP
    "DKK" -> rates.DKK
    "HUF" -> rates.HUF
    "CZK" -> rates.CZK
    "AUD" -> rates.AUD
    "RON" -> rates.RON
    "SEK" -> rates.SEK
    "IDR" -> rates.IDR
    "INR" -> rates.INR
    "BRL" -> rates.BRL
    "RUB" -> rates.RUB
    "HRK" -> rates.HRK
    "JPY" -> rates.JPY
    "THB" -> rates.THB
    "CHF" -> rates.CHF
    "SGD" -> rates.SGD
    "PLN" -> rates.PLN
    "BGN" -> rates.BGN
    "CNY" -> rates.CNY
    "NOK" -> rates.NOK
    "NZD" -> rates.NZD
    "ZAR" -> rates.ZAR
    "USD" -> rates.USD
    "MXN" -> rates.MXN
    "ILS" -> rates.ILS
    "GBP" -> rates.GBP
    "KRW" -> rates.KRW
    "MYR" -> rates.MYR
    else -> null
}}

CurrencyApi

interface CurrencyApi {

@GET("/v6/68c54e50f924117c29176f8f/latest/USD")
suspend fun getRates(
    @Query("base_code") base_code : String
): Response<CurrencyResponse>}

Default Main Repo

class DefaultMainRepository @Inject constructor(
private val api :CurrencyApi): MainRepository {
override suspend fun getRates(base_code: String): Resource<CurrencyResponse> {

    return try {
        val response = api.getRates(base_code)
        val result= response.body()
        if (response.isSuccessful && result!=null){
            Resource.Success(result)
        } else{
            Resource.Error(response.message())
        }
    }catch (e: Exception) {
        Resource.Error(e.message?: "An error occured")
    }
}}

MainRepo:

interface MainRepository {

suspend fun getRates(base_code: String) : Resource<CurrencyResponse>}

Upvotes: 0

Views: 364

Answers (2)

Ayia
Ayia

Reputation: 156

Apperently, kotlin data classes can only contain about 127 arguments, so you have two options.

  1. Break down the response data class into two (RatesAToL and RatesMToZ) and make two requests to the API.

  2. Convert the JSON object (conversion_rates) into a list of objects Rate(val code: String, val amount: Double) . This can be done using a Moshi custom adapter.

See code gist here Custom Moshi adapter Full project on GitHub fxConverter

class MoshiJsonAdapter : JsonAdapter<InfoAndRatesResponse>() {

    @FromJson
    override fun fromJson(reader: JsonReader): InfoAndRatesResponse {

    reader.beginObject()

    var result = ""
    var timeLastUpdateUnix = 0
    var timeLastUpdateUtc = ""
    var timeNextUpdateUnix = 0
    var timeNextUpdateUtc = ""
    var baseCode = ""
    val rates = mutableListOf<Rate>()

    while (reader.hasNext()) {
        when (reader.nextName()) {
            "result" -> result = reader.nextString()
            "time_last_update_unix" -> timeLastUpdateUnix = reader.nextInt()
            "time_last_update_utc" -> timeLastUpdateUtc = reader.nextString()
            "time_next_update_unix" -> timeNextUpdateUnix = reader.nextInt()
            "time_next_update_utc" -> timeNextUpdateUtc = reader.nextString()
            "base_code" -> baseCode = reader.nextString()
            "conversion_rates" -> {

                reader.beginObject()

                while (reader.peek() != JsonReader.Token.END_OBJECT) {

                    val code = reader.nextName()

                    val amount = when (code) {
                        baseCode -> reader.nextInt().toDouble()
                        else -> reader.nextDouble()
                    }

                    val infoBaseCode = baseCode

                    Timber.tag("MyTag")
                        .d(" code $code amount $amount infoBaseCode $infoBaseCode")

                    rates.add(
                        Rate(
                            code = code,
                            amount = amount,
                            infoBaseCode = infoBaseCode,
                        )
                    )
                }
                reader.endObject()
            }
            else -> reader.skipValue()
        }
    }
    reader.endObject()

    return InfoAndRatesResponse(
        result = result,
        timeLastUpdateUnix = timeLastUpdateUnix,
        timeLastUpdateUtc = timeLastUpdateUtc,
        timeNextUpdateUnix = timeNextUpdateUnix,
        timeNextUpdateUtc = timeNextUpdateUtc,
        baseCode = baseCode,
        rates = rates
     )
    }

   @ToJson
    override fun toJson(writer: JsonWriter, value: 
    InfoAndRatesResponse?) {
    throw UnsupportedOperationException()
    }
   }

Upvotes: 1

John Oberhauser
John Oberhauser

Reputation: 466

I'm not sure about this, but when it says "Unable to invoke no-arg constructor", to me that sounds like you need to be able to create an instance of CurrencyResponse with no arguments. Maybe try setting some default values? I would give this a shot:

data class ConversionRates(
    val AED: Double = 0.0,
    val AFN: Double = 0.0,
    val ALL: Double = 0.0,
    val AMD: Double = 0.0,
    val ANG: Double = 0.0,
    val AOA: Double = 0.0,
    val ARS: Double = 0.0,
    val AUD: Double = 0.0,
    val AWG: Double = 0.0,
    val AZN: Double = 0.0,
    val BAM: Double = 0.0,
    val BBD: Double = 0.0,
    val BDT: Double = 0.0,
    val BGN: Double = 0.0,
    val BHD: Double = 0.0,
    val BIF: Double = 0.0,
    val BMD: Double = 0.0,
    val BND: Double = 0.0,
    val BOB: Double = 0.0,
    val BRL: Double = 0.0,
    val BSD: Double = 0.0,
    val BTN: Double = 0.0,
    val BWP: Double = 0.0,
)

Upvotes: 0

Related Questions