I'm pulling values like conversion_rates from the exchange api and I don't want to use all of these values. Because there are data that I do not want to use in the data coming from the api. In addition, I want to save the data I want to use in the room and perform operations such as reading and updating from there. But I couldn't find how to convert the data class to room entity and the data class I want to show in ui.
The data in the api is as follows, for example. This example uses USD currency
{
"result":"success",
"documentation":"https://www.exchangerate-api.com/docs",
"terms_of_use":"https://www.exchangerate-api.com/terms",
"time_last_update_unix":1669852801,
"time_last_update_utc":"Thu, 01 Dec 2022 00:00:01 +0000",
"time_next_update_unix":1669939201,
"time_next_update_utc":"Fri, 02 Dec 2022 00:00:01 +0000",
"base_code":"USD",
"conversion_rates":{
"USD":1,
"AED":3.6725,
"AFN":88.4843,
"ALL":112.6064,
"AMD":395.1203,
"ANG":1.7900,
"AOA":509.9729,
"ARS":166.6930,
"AUD":1.4810,
"AWG":1.7900,
"AZN":1.6987,
"BAM":1.8843,
"BBD":2.0000,
"BDT":101.1341,
"BGN":1.8844,
"BHD":0.3760,
"BIF":2059.0872,
"BMD":1.0000,
"BND":1.3652,
"BOB":6.9223,
"BRL":5.2810,
"BSD":1.0000,
"BTN":81.2806,
"BWP":12.8706,
"BYN":2.8013,
"BZD":2.0000,
"CAD":1.3488,
"CDF":2054.0886,
"CHF":0.9472,
"CLP":900.6901,
"CNY":7.0769,
"COP":4807.5892,
"CRC":599.5200,
"CUP":24.0000,
"CVE":106.2304,
"CZK":23.4794,
"DJF":177.7210,
"DKK":7.1874,
"DOP":54.5873,
"DZD":138.5566,
"EGP":24.5835,
"ERN":15.0000,
"ETB":53.4589,
"EUR":0.9631,
"FJD":2.2145,
"FKP":0.8319,
"FOK":7.1874,
"GBP":0.8317,
"GEL":2.7155,
"GGP":0.8319,
"GHS":14.2807,
"GIP":0.8319,
"GMD":63.3901,
"GNF":8622.0204,
"GTQ":7.8226,
"GYD":209.1401,
"HKD":7.8054,
"HNL":24.6994,
"HRK":7.2588,
"HTG":140.6488,
"HUF":393.5594,
"IDR":15633.2624,
"ILS":3.4355,
"IMP":0.8319,
"INR":81.2818,
"IQD":1458.8962,
"IRR":42024.7114,
"ISK":141.8861,
"JEP":0.8319,
"JMD":154.0163,
"JOD":0.7090,
"JPY":138.2686,
"KES":122.8172,
"KGS":84.4061,
"KHR":4125.2930,
"KID":1.4813,
"KMF":473.9665,
"KRW":1311.7736,
"KWD":0.2996,
"KYD":0.8333,
"KZT":469.2553,
"LAK":17329.4289,
"LBP":1507.5000,
"LKR":363.3087,
"LRD":153.9036,
"LSL":17.0637,
"LYD":4.8894,
"MAD":10.7013,
"MDL":19.4034,
"MGA":4347.5707,
"MKD":59.4442,
"MMK":2452.0310,
"MNT":3422.5815,
"MOP":8.0398,
"MRU":38.0126,
"MUR":43.6263,
"MVR":15.4107,
"MWK":1029.7547,
"MXN":19.2883,
"MYR":4.4641,
"MZN":64.0629,
"NAD":17.0637,
"NGN":443.7037,
"NIO":36.3802,
"NOK":9.8760,
"NPR":130.0490,
"NZD":1.5947,
"OMR":0.3845,
"PAB":1.0000,
"PEN":3.8417,
"PGK":3.5202,
"PHP":56.3904,
"PKR":223.9704,
"PLN":4.4951,
"PYG":7228.9088,
"QAR":3.6400,
"RON":4.7382,
"RSD":113.3592,
"RUB":60.9665,
"RWF":1099.6940,
"SAR":3.7500,
"SBD":8.1340,
"SCR":13.0123,
"SDG":568.2933,
"SEK":10.5247,
"SGD":1.3649,
"SHP":0.8319,
"SLE":18.6210,
"SLL":18620.9957,
"SOS":568.2695,
"SRD":30.9539,
"SSP":643.6471,
"STN":23.6035,
"SYP":2502.5362,
"SZL":17.0637,
"THB":35.2322,
"TJS":10.1162,
"TMT":3.4979,
"TND":3.1050,
"TOP":2.3745,
"TRY":18.6339,
"TTD":6.7542,
"TVD":1.4813,
"TWD":30.6481,
"TZS":2333.7110,
"UAH":36.4908,
"UGX":3747.1520,
"UYU":39.4702,
"UZS":11228.0193,
"VES":11.0789,
"VND":24625.3969,
"VUV":121.1513,
"WST":2.7337,
"XAF":631.9553,
"XCD":2.7000,
"XDR":0.7612,
"XOF":631.9553,
"XPF":114.9656,
"YER":250.0464,
"ZAR":17.0568,
"ZMW":17.0409,
"ZWL":653.4299
}
}
but here, for example, I don't want to save
time_next_update_utc,
"time_next_update_unix",
"time_last_update_utc,
time_last_update_unix,
terms_of_use",
documentation
data to the room, and I don't want to show them to the user either.
because data is useless to me. I just want to save the following data in the room and show it to the user
"result",
"base_code",
"conversion_rates"
and I prepared something like that.
Room
Entity
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "allData") val allData: AllData,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
Dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET ")
suspend fun update()
}
Remote
Dto
#Serializable
data class AllData(
val allData:List<ExchangeDto>
)
#Serializable
data class ExchangeDto(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
)
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
In this Dto, I wrote extension functions such as toEntity to convert it to an entity or to convert it to a model class that I will display ui, but I couldn't make it up. I'm a little lacking in this
Repository
interface ExchangeRepository {
suspend fun getAll() : Flow<List<AllData>>
suspend fun update(exchange: ExchangeDto)
}
RepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<AllData>> {
return flow {
emit(dao.getAll().map {it.allData}) // this part is wrong
}
}
override suspend fun update(exchange: ExchangeDto) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates) // While updating, it is important that it is compatible with my entity class. There is also a problem here.
}
}
I guess I didn't prepare the dto properly. Actually, I'm sorry that I may have shared the codes a bit incompletely and badly because I don't know exactly what to do, but I hope you understand what I want to do.
The problem is when I getAll in the ExchangeRepositoryImpl , the ExchangeEntity is constantly coming, and I can't translate it either. At the same time, I couldn't quite figure out how to adapt the update in ExchangeRepositoryImpl to them. because i need to update data every 24 hours according to data in api.
Sorry for the poor title but it is rather hard to describe my use case in a short sentence.
Context
I have the following model:
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
And I have three main data structures in my use case:
A Set of Identifiers that exist and are valid in a given context. Example:
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "B-2", "C-1")
A Map that contains a List of Data objects per Identifier. Example:
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
A List of Lists that group together the Identifiers that should share the same List<Data> (each List includes always 2 Identifiers). Example
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
Problem / Use Case
The problem that I am trying to tackle stems from the fact that dataPerIdentifier might not contain all identifiersWithSameData given that existentIdentifiers contains such missing Identifiers. I need to add those missing Identifier to dataPerIdentifier, copying the List<Data> already in there.
Example
Given the data in the Context section:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)]
The desired outcome is to update dataPerIdentifier so that it includes:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)],
A-2=[Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A)]
The reason is that existentIdentifiers contains A-2 that is missing in the initial dataPerIdentifier Map. B-2 is also missing in the initial dataPerIdentifier Map but existentIdentifiers does not contain it, so it is ignored.
Possible solution
I have already a working code (handleDataForMultipleIdentifiers() method is the one doing the heavy lifting), but it does not feel to be the cleanest or easiest to read:
fun main(args: Array<String>) {
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "C-1")
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
print("Original Data")
println(dataPerIdentifier)
print("Target Data")
println(dataPerIdentifier.handleDataForMultipleIdentifiers(identifiersWithSameData, existentIdentifiers))
}
fun Map<Identifier, List<Data>>.handleDataForMultipleIdentifiers(identifiersWithSameData: List<List<Identifier>>, existentIdentifiers: Set<Identifier>)
: Map<Identifier, List<Data>> {
val additionalDataPerIdentifier = identifiersWithSameData
.mapNotNull { identifiersList ->
val identifiersWithData = identifiersList.find { it in this.keys }
identifiersWithData?.let { it to identifiersList.minus(it).filter { it in existentIdentifiers } }
}.flatMap { (existentIdentifier, additionalIdentifiers) ->
val existentIdentifierData = this[existentIdentifier].orEmpty()
additionalIdentifiers.associateWith { identifier -> existentIdentifierData.map { it.copy(identifier = identifier) } }.entries
}.associate { it.key to it.value }
return this + additionalDataPerIdentifier
}
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
So my question is: how can I do this in a simpler way?
If identifiersWithSameData always contains 2 identifiers per item then it should not really be a list of lists, but rather a list of pairs or dedicated data classes. And if you convert this data structure into a map like this:
val identifiersWithSameData = mapOf("A-1" to "A-2", "A-2" to "A-1", "B-1" to "B-2", "B-2" to "B-1")
The the whole solution is pretty simple:
existentIdentifiers.associateWith {
dataPerIdentifier[it] ?: dataPerIdentifier[identifiersWithSameData[it]!!]!!
}
I'm not sure about both !!, for example I don't know if it is guaranteed that identifier existing in existentIdentifiers exists in identifiersWithSameData as well. You may need to tune this solution a little.
I cannot find anything about this type of relation (everything is about one-to-one, one-to-many or many-to-many). And even those look a little bit too complicated for what I need.
I have a table with tasks and a table with images. Multiple tasks can have the same image in order to save space (the image is not deleted when the task is deleted). I have an entity for the task
import android.graphics.Bitmap
import androidx.room.*
#Entity(tableName = "tasks")
class Task(
#PrimaryKey(autoGenerate = true)
val id: Long,
val imageId: Long = 0,
// this needs a foreign key to Image
val image: Image
)
and another one for the image
import androidx.room.ColumnInfo
import androidx.room.PrimaryKey
#Entity(tableName = "images")
class Image (
#PrimaryKey(autoGenerate = true)
val id: Long,
val title: String,
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val data: ByteArray? = null
)
How do I add a foreignKey for the imageId column so that it points to an Image? Can I directly obtain an Image object as a member of Task without having to create another class?
You have to use one-to-many relationship here. There is an example from d.android.com. If you will do like that you will have a Task entity, an Image entity and data class TaskWithImage, just like in the example provided.
data class TaskWithImage(
#Embedded val task: Task,
#Embedded(prefix = "img_") val image: Image
)
#Query("""
SELECT * FROM tasks
INNER JOIN images as img_ ON tasks.id = img_.id
""")
fun getTasksWithImage(): List<TaskWithImage>
I parsed a json string to the following object structure using gson:
data class Base (
val expand: String,
val startAt: Long,
val maxResults: Long,
val total: Long,
val issues: List<Issue>
)
data class Issue (
val expand: String,
val id: String,
val self: String,
val key: String,
val fields: Fields
)
data class Fields (
val summary: String,
val issuetype: Issuetype,
val customfield10006: Long? = null,
val created: String,
val customfield11201: String? = null,
val status: Status,
val customfield10002: Customfield10002? = null,
val customfield10003: String? = null
)
Everything works fine and also the object model is correct, because I can access each element of the object.
However, I encountered the problem that I dont know how to get a list of all field-elements. Right now, I have only figured out how to access one item (by using an index and get()-function):
val baseObject = gson.fromJson(response, Base::class.java)
val fieldsList = baseObject.issues.get(0).fields
I actually want to have a list of all field elements and not just one. Is there a gson function allowing me to do that? I couldn't find anything about it in the gson documentation for java.
You don't have to look for some gson function when you've already created a baseObject. You just need to get from each issue it's fields and you can use a map function to achieve this, it will convert each issue to a new type so you can get issue fields there
val fieldFromAllIssues: List<Fields> = baseObject.issues.map { it.fields }
it in this context is a one issue. More explanation about it is here