I've this json :
{
"products": [{
"id": "150",
"num": "7",
"name": "450 SA"
}, {
"id": "122",
"num": "13",
"name": "Gillette Blue"
}]}
I've created my models from it , i've these classes for it :
#Entity
data class ProductsModel(
#Json(name = "products")
val products: List<Product>
)
#Entity
data class Product(
#PrimaryKey(autoGenerate = false)
val id: String,
#Json(name = "name")
val name: String,
#Json(name = "num")
val num: String,
)
this is my DAO class for inserting data into my room database :
#Dao
interface ProductsDAO {
// 2: Insert
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(product: ProductsModel)
When I want to run the application , I get this error :
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
How can I save these data into my database ?
Room provides functionality for converting between primitive and boxed types but doesn't allow for object references between entities.
You can simply create a table that has 3 columns - id, name and num. So each row would be a different Product
OR
Your data base should only save the list of Product's and you should provide a TypeConverter, which converts a Product class to and from a known type that Room can persist.
more about type converters - link
Related
I'm using an API to retrieve some data, and store them in my app DB using Room, Retrofit2, and GSON.
My data object is as follow:
#Entity(tableName = "department")
data class Department(
val nom: String,
#PrimaryKey val code: String,
val region: String
)
And this is what the API returns me
{
"nom": "Ain",
"code": "01",
"region": {
"code": "84",
"nom": "Auvergne-Rhône-Alpes"
}
}
I want to transform the response region.nom as the data region field. My actual solution is to make an interface object that can store the response, then a function for mapping this interface to my data object. But i'm pretty sure there is better/cleaner solution to achieve this (like maybe TypeConverter, but can't understand how it works).
Thanks :
Assume your retrofit api response object name "response".You can simply do this :
var department = Department(response.nom,
response.region.nom)
Then just pass the "department" object to room db insert function.
The data that I want to use has this structure:
{
"1": {
"id": 1,
"name": "Bulbasaur"
},
"2": {
"id": 2,
"name": "Ivysaur"
},
"3": {
"id": 3,
"name": "Venusaur"
}
}
Note:
The number labeling each object matches the id of the Pokémon, not the number of Pokémon
My problem is that when I try to create data classes for this it ends up creating a data class for each object. Not one data class that fits each object. I believe this is due to the number labeling the object(Pokémon) being different for each object.
Is there a way I can format this data in maybe one or two data classes and not over 800?
Ideally I would like the data to be structured like this but it does not work when run.
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
When parsing Json to Object with this special case, you should custom Json Deserializer yourself.
Here I use Gson library to parse Json to Object.
First, create a custom Json Deserializer with Gson. As follows:
PokemonResponse.kt
data class PokemonResponse(
val pokemonMap: List<StringReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
GsonHelper.kt
object GsonHelper {
fun create(): Gson = GsonBuilder().apply {
registerTypeAdapter(PokemonResponse::class.java, PokemonType())
setLenient()
}.create()
private class PokemonType : JsonDeserializer<PokemonResponse> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): PokemonResponse {
val list = mutableListOf<ReleasedPokemonModel>()
// Get your all key
val keys = json?.asJsonObject?.keySet()
keys?.forEach { key ->
// Get your item with key
val item = Gson().fromJson<ReleasedPokemonModel>(
json.asJsonObject[key],
object : TypeToken<ReleasedPokemonModel>() {}.type
)
list.add(item)
}
return PokemonResponse(list)
}
}
}
Next I will create a GsonConverterFactory so that I can addConvertFactory to Retrofit.
val gsonConverterFactory = GsonConverterFactory.create(GsonHelper.create())
And now I will add retrofit.
val retrofit = Retrofit.Builder()
// Custom your Retrofit
.addConverterFactory(gsonConverterFactory) // Add GsonConverterFactoty
.build()
Finally in ApiService, your response will now return type PokemonResponse.
interface ApiService {
#GET("your_link")
suspend fun getGenres(): PokemonResponse
}
The problem is that there's no JSON array there. it's literally one JSON object with each Pokemon listed as a property. I would recommend that you reformat the JSON beforehand to look like this:
[
{
"id": 1,
"name": "Bulbasaur"
},
{
"id": 2,
"name": "Ivysaur"
},
{
"id": 3,
"name": "Venusaur"
}
]
And then you could model it like this:
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
data class Response(
val items: List<ReleasedPokemonModel>
)
See more here.
And see here for discussion about reformatting the data before handing it to Retrofit.
You can use Map to store the key like the following
data class PokemonResponse(
val pokemonMap:Map<String,ReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
I have a Json similar to below format:
{
"gender": "male",
"name": {
"first": "Axel",
"last": "Raab"
}
}
I want to create 2 tables, say Person and Name and store this data in these 2 tables.
My Entity classes look like these:
#Entity
data class Person(
#PrimaryKey(autoGenerate = true)
var pid: Int,
#SerializedName("gender")
var gender: String?,
#SerializedName("name")
#Ignore
var name: Name?,
)
#Entity(indices = [Index("pId")],
foreignKeys = [ForeignKey(
entity = Person::class,
parentColumns = ["pid"],
childColumns = ["pId"],
onDelete = ForeignKey.CASCADE)])
data class Name(
#PrimaryKey(autoGenerate = true)
val mId: Int,
#SerializedName("first")
var first: String?,
#SerializedName("last")
var last: String?,
var pId: Int
)
I have created corresponding Daos but unable to identify a way where I can write a transaction that would store this data into 2 tables and relate then using provided ids.
I have gone through many solutions that would either use #Embedded or #Relation or TypeConverter approaches but either they do not fit my use case or the solution is about querying the data and not how to save or insert.
Thanks.
You can use Embedded
e.g
public class Coordinates {
double latitude;
double longitude;
}
public class Address {
String street;
#Embedded
Coordinates coordinates;
}
Insert example in your DAO
#Insert
fun insert(item: Coordinates)
you can pass Coordinates instance variable in your insert function
Address("your street", Coordinates(your_lat,your_lng))
I have a question that maybe will be easy for some of you but I really can't solve.
I have a json formatted like this:
{
"id" : "1641S818",
"balance" : "100.20",
"transactions" : [
{
"id" : "item1",
"price" : "1.50",
"description" : "pen",
"date" : "2018-05-14T14:19:00Z"
},
{
"id" : "item1",
"price" : "9.00",
"description" : "book",
"date" : "2018-05-14T08:19:00Z"
}
]
}
I wanted to try and put up an app with Retrofit + Room for the first time, and I'm having trouble building models.
I came up with this data classes for Retrofit and they work fine. This is no surprise as I know how to use it.
data class Account (
val id : String,
val balance : Double,
val transactions : List<Transaction>
)
data class Transaction (
val id : String,
val price : Double,
val description : String,
val date : String
)
The real issues start when I want to save this structure in a database, with this one-to-many relationship existing between the Account and the Transaction. So far I understood that Room cannot handle this type of structure and I should build different classes for the database model. So I did:
#Entity(tableName = "account")
data class AccountData(
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = false)
val id: String,
val balance: Double,
)
#Entity(
tableName = "transaction",
foreignKeys = [
ForeignKey(
entity = AccountData::class,
parentColumns = ["id"],
childColumns = ["account_id"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)
]
)
data class TransactionData(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "id")
val transactionId: String,
#ColumnInfo(name = "account_id")
val accountId: String,
val price: Double,
val description: String,
val date: String
)
Now the part where I'm stuck. I cannot find a guide, an example, anything that shows where to convert the models with a mapper (maybe using livedata or rx) with this particular case, which is when we have complex relations between objects and not just plain classes.
You can define separate Mapper file with extension functions like this
fun Transaction.toTransactionData() = TransactionData(...)
And then after API call, probably in UseCase, you can use this function to map your API entity to DB entity and then pass result to Room dao.
UPD 1.
Additionally you can define Transation entity for UI usage. So at UseCase level you operate with RestTransaction and DbTransaction, but passing Transaction to UI, to abstract from its source.
I have two objects: Author and Book.
#RealmClass
class Author {
#PrimaryKey
val id: String?
val books: RealmList<Book> = RealmList()
}
#RealmClass
class Book {
#PrimaryKey
val id: String?
val countPages: Long
val genre: String
}
And I have data in realm, like this:
{
"id": "author1",
"books": [
{
"id": "book1",
"countPages": 100,
"genre": "fantasy"
},
{
"id": "book2",
"countPages": 150,
"genre": "non-fiction"
}
]
}
I want to find authors with books, which have specific genre and specific pages count. If I write something like this:
realmQuery.where().equalsTo("books.countPages", 100).equalsTo("books.genre", "non-fiction").find()
I'll get one author with id = author1. But it's not true, I should get empty list.
How can I write query to achieve this?
Link queries translate to has at least one of ___ where X is true, so
.equalsTo("books.countPages", 100).equalsTo("books.genre", "non-fiction")
Says "author that has at least one book that has countPages 100, and has at least one book that has genre non-fiction" -- which is true! But it is not what you want.
There are two ways to go about this:
1.) query the existing result set to get a "smaller" result:
realmQuery.where()
.equalTo("books.countPages", 100)
.findAll()
.equalTo("books.genre", "non-fiction")
.findAll()
2.) execute the query on Books, and access the Author via linking objects inverse relationship
#RealmClass
class Book {
#PrimaryKey
val id: String?
val countPages: Long
val genre: String
#LinkingObjects("books")
val authors: RealmResults<Author>? = null
}
And
val books = realm.where<Book>().equalTo("countPages", 100).equalTo("genre", "non-fiction").findAll();
// these books have `authors` field that contains the author