Ok, i have a problem in a Kotlin multiplatform project i am working on at the moment.
I want to get some Currency conversion rates from a api (http://www.floatrates.com/daily/usd.json) and save them in a local Database (Realm).
The problem i am currently facing is, that all the data in the JSON is saved in many many uniquely named objects and not in a Array (these objects all have the same structure).
{
"aud": {
"code": "AUD",
"alphaCode": "AUD",
"numericCode": "036",
"name": "Australian Dollar",
"rate": 1.5120512400088,
"date": "Wed, 13 Mar 2024 12:55:07 GMT",
"inverseRate": 0.66135324884504
},
"cad": {
"code": "CAD",
"alphaCode": "CAD",
"numericCode": "124",
"name": "Canadian Dollar",
"rate": 1.3495144747695,
"date": "Wed, 13 Mar 2024 12:55:07 GMT",
"inverseRate": 0.74100724274989
},
.
.
// hundreds more
.
.
"inr": {
"code": "INR",
"alphaCode": "INR",
"numericCode": "356",
"name": "Indian Rupee",
"rate": 82.850734958333,
"date": "Wed, 13 Mar 2024 12:55:07 GMT",
"inverseRate": 0.012069898963515
},
"jpy": {
"code": "JPY",
"alphaCode": "JPY",
"numericCode": "392",
"name": "Japanese Yen",
"rate": 147.78881616883,
"date": "Wed, 13 Mar 2024 12:55:07 GMT",
"inverseRate": 0.0067664118701486
}
}
I want to save those currencies in some iterable Data structure (for example Map<String,Currency>
) with:
data class Currency(
val code: String,
val alphaCode: String,
val numericCode: String,
val name: String,
val rate: Double,
val date: String,
val inverseRate: Double,
)
But i cant get Retrofit to store the data into my map.
If i use the Json to kotlin class plugin for android studio i can generate a class like this:
data class AutoGeneratedResponseDataClass(
val aed: Aed,
val afn: Afn,
val all: All,
val amd: Amd,
.
.
// hundreds more
.
.
val xpf: Xpf,
val yer: Yer,
val zar: Zar,
val zmw: Zmw,
)
with hundereds of other classes like these that are all exactly the same
data class Aed(
val alphaCode: String,
val code: String,
val date: String,
val inverseRate: Double,
val name: String,
val numericCode: String,
val rate: Double
)
.
.
.
data class Zmw(
val alphaCode: String,
val code: String,
val date: String,
val inverseRate: Double,
val name: String,
val numericCode: String,
val rate: Double
)
I think technically it is possioble to iterate through all fields of an object by using reflection, but and i could change all those class types to one single class, but still, thats not really what i want.
To test if everything is working i have a Dialog that displays the json in form of a string and two buttons, one for a test api that works fine and one for this api that just returns "null", no matter what i try
Button(
onClick = { viewModel.getCurrencies() }
) {
Text(text = "sync currencies")
}
Button(
onClick = { viewModel.getQuotes() }
) {
Text(text = "sync quotes")
}
Button(
onClick = { viewModel.showDialog = true }
) {
Text(text = "Show Response")
}
the code for the test api is:
//This code works and gets me the desired output
object RetrofitHelperQuotes {
val baseUrl = "https://quotable.io/"
fun getInstance(): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
interface QuotesApi {
@GET("/quotes")
suspend fun getQuotes() : Response<QuoteList>
}
//with
data class QuoteList(
val count: Int,
val lastItemIndex: Int,
val page: Int,
val results: List<Result>,
val totalCount: Int,
val totalPages: Int
)
data class Result(
val _id: String,
val author: String,
val authorSlug: String,
val content: String,
val dateAdded: String,
val dateModified: String,
val length: Int,
val tags: List<String>
)
// and this code in the viewmodel that gets called on button press
@OptIn(DelicateCoroutinesApi::class)
fun getQuotes(){
val quotesApi = RetrofitHelperQuotes.getInstance().create(QuotesApi::class.java)
GlobalScope.launch {
val result = quotesApi.getQuotes()
if (result != null)
//this is the string that gets stored in the viewModel and gets displayed onscreen when i press the third button
jsonResponse = GsonBuilder().setPrettyPrinting().create().toJson(result.body())
}
}
and the code for the api i actually want to use is:
object RetrofitHelperCurrency {
val baseUrl = "http://www.floatrates.com/daily/"
fun getInstance(): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
interface CurrencyApi {
@GET("/usd.json")
suspend fun getCurrencies() : Response<AutoGeneratedResponseDataClass>
@GET("/usd.json")
suspend fun anotherTry() : Response<Map<String,Currency>>
@GET("/usd.json")
suspend fun yetAnotherTry() : Response<CurrencyApiResponse>
}
//with
data class CurrencyApiResponse(
val data: Map<String,Currency>,
)
// and the rest of my classes as described at the top
// and this code in the viewmodel that gets called on button press
@OptIn(DelicateCoroutinesApi::class)
fun getCurrencies(){
val currencyApi = RetrofitHelperCurrency.getInstance().create(CurrencyApi::class.java)
GlobalScope.launch {
val result = currencyApi.getCurrencies()
if (result != null)
//this is the string that gets stored in the viewModel and gets displayed onscreen when i press the third button
jsonResponse = GsonBuilder().setPrettyPrinting().create().toJson(result.body())
}
}
After all that is resolved i want to store the data in a local realm database in case i lose internet connection or something like that, but i am currently stuck at this problem and cant get it to work.
In general i am kina new to programming and this is jsut a "small" learning project for me, but even after googeling my ass off and trying countless things i cant get it to work, so any help is appreciated.
Try removing the "/" in front of front of "/usd.json".
Using "/usd.json" results in the following URI: "http://www.floatrates.com/usd.json". But the correct one is "http://www.floatrates.com/daily/usd.json". Tested with wireshark.
The following worked for me to extract the result into a currency list:
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
interface CurrencyService {
@GET("usd.json")
fun getUsd(): Call<Map<String, Currency?>?>?
}
data class Currency(
val code: String,
val alphaCode: String,
val numericCode: String,
val name: String,
val rate: Double,
val date: String,
val inverseRate: Double
)
fun main() {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.floatrates.com/daily/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(CurrencyService::class.java)
val currencyMap = service.getUsd()?.execute()?.body()
val currencies = currencyMap?.values
println("repos: ${currencies}")
}