Saving Multiple Nested lists into room database - android

I'm working on dictionnary application in which the api request have many nested lists , i have tried to insert all the nested lists but i'm getting different error each time , i would like to know what is the best way to save multiple nested lists , should i use room relations or something else , thank you in advance for help , i m really stuck with this for few days now
This is sample schema of how are the lists nested
This is the parent list
#Entity(tableName = "DICTIONNARYTABLE")
#TypeConverters(DictionnaryModelConverter::class)
class DictionnaryModel : ArrayList<DictionnaryModelItem>() {
#PrimaryKey(autoGenerate = true)
#NotNull
val wordId: Long = 0
}
The parent list has two lists as well
#Entity
data class DictionnaryModelItem(
#PrimaryKey val dictionnaryModelId: Long = 0,
#TypeConverters(DictionnaryMeaningConverter::class)
val meanings: MutableList<Meaning>,
#TypeConverters(DictionnaryPhoneticsConverter::class)
val phonetics: MutableList<Phonetic>,
val word: String
)
//---------------------------
#Entity
data class Meaning(
#PrimaryKey val meaningId: Long = 0,
#TypeConverters(DictionnaryDefinitionConverter::class)
val definitions: List<Definition>,
val partOfSpeech: String
)
///-------------------------------
#Entity
data class Phonetic(
#PrimaryKey val phoneticId: Long = 0,
val audio: String,
val text: String
)
inside the meaning , i also have definition which another model
#Entity
data class Definition(
#PrimaryKey val definitionId: Long = 0,
val definition: String,
val example: String,
#TypeConverters(DictionnarySynonymsConverter::class)
val synonyms: List<String>
)

You need to create one-to-many relationship data model here. For instance each dictionary word has many meanings and many phonetics. Here Dictionary is a parent entity and Meaning and Phonetic are the child entities. Each child entity will have it's parent entity primary key stored in its table. You will need another data class to define this relationship.
data class DictionaryWithMeanings(
#Embedded val dictionary: Dictionary,
#Relation(
parentColumn = "dictionaryModelId",
entityColumn = "dictionaryId"
)
val meanings: List<Meaning>
)
Meaning table has to store dictionaryId as foreign key its table. Same has to be defined for phonetics. And Meaning table again has similar relationship with Definition and so on.

Related

What do #Relation classes mean when creating a one-to-many relationship in Room?

I'm making an Workout log app.
One Workout has multiple sets.
I want to store this in a one-to-many relationship in Room.
In conclusion, I succeeded in saving, but I'm not sure what one class does.
All of the other example sample code uses this class, so I made one myself, but it doesn't tell me what it means.
WorkoutWithSets
data class WorkoutWithSets(
#Embedded val workout: Workout,
#Relation (
parentColumn = "workoutId",
entityColumn = "parentWorkoutId"
)
val sets: List<WorkoutSetInfo>
)
The following two entity classes seem to be sufficient to express a one-to-many relationship. (Stored in Room)
Workout
#Entity
data class Workout(
#PrimaryKey(autoGenerate = true)
var workoutId: Long = 0,
var title: String = "",
var memo: String = "",
)
It seems that the following two entity classes are sufficient enough to store a one-to-many relationship.. (stored in Room)
WorkoutSetInfo
#Entity(
foreignKeys = [
ForeignKey(
entity = Workout::class,
parentColumns = arrayOf("workoutId"),
childColumns = arrayOf("parentWorkoutId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class WorkoutSetInfo(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val set: Int,
var weight: String = "",
var reps: String = "",
var unit: WorkoutUnit = WorkoutUnit.kg,
val parentWorkoutId: Long = 0
)
Even if the WorkoutWithSet class does not exist, the Workout and WorkoutSetInfo classes are stored in Room.
What does WorkoutWithSets class mean? (or where should I use it?)
What does WorkoutWithSets class mean?
It is a class that can be used to retrieve a Workout along with all the related WorkoutSetInfos via a simple #Query that just retrieves the parent Workouts.
What Room does is add an additional query that retrieves the children (WorkoutSetInfo's) for each Workout.
The result being a list of WorkOutWithSets each element (a Workout) containing/including a list of all the related WorkoutSetInfo's.
You would use this when you want to process a Workout (or many Workouts) along with the related WorkoutSetInfo's (aka the child WorkoutSetInfo's for the parent Workout).
What Room does is consider the type (objects) to be returned.
So if you had
#Query("SELECT * FROM workout")
fun getJustWorkouts(): List<Workout>
then the function would return just a list of Workout objects.
But if you had
#Query("SELECT * FROM workout")
fun getWorkoutsWithSets(): List<WorkoutWithSets>
then the function would return a list of WorkoutWithSets and thus the parent Workouts with the child WorkoutSetInfo's.
What Room does is build and execute an underlying query, for eack Workout extracted, along the lines of "SELECT * FROM workoutInfoSet WHERE workout.workoutId = parentWorkoutId" and hence why it suggests the use of the #Transaction annotation (the build will include a warning if #Transaction is not coded).

Can I create multiple tables with one data class?

What I wanna do is, By merging the following two data classes into one, the two tables are divided as they are.
#Entity(tableName = "file")
data class File(
#PrimaryKey
#ColumnInfo(name = "path") var path: String,
#ColumnInfo(name = "date", index = true) var date: Long,
#ColumnInfo(name = "number") var num: Float = -1f
)
#Entity(tableName = "del_file")
data class delFile(
#PrimaryKey
#ColumnInfo(name = "path") var path: String,
#ColumnInfo(name = "date", index = true) var date: Long,
#ColumnInfo(name = "number") var num: Float = -1f
)
The reason I want to manage those two tables separately is that they are used in completely different situations.
'file' will be used in the app's file system, and 'del_file' will be managed only in the recycle bin.
I also thought about adding and managing a column called "is_alive" to the "file" table, but I thought it was a bad structure in that it would be meaningless data in almost all entries and that filtering was required for all queries to be used in the app's file system.
The best way I think is to manage each of the two tables, but I thought I couldn't come up with a better way because I wasn't good enough.
Is there a way to manage the table separately while making that data class one? Or is there a better way?
I would be very, very grateful if you could tell me a good way.
Generally, having two separate tables for the same data model is not good because it may lead to data duplication, which has various disadvantages; for example, it costs storage space and data inconsistency, etc.
There are two ways to deal with this situation. If you want to distinguish file items just by one field(for example, is_alive), the best way is to use one table with the is_alive field. You can use this way to solve your problem.
But if those distinguishable fields are more than one or maybe in the future become more, the solution is to create another table(like def_file) that contains only the reference to the original table(file table) and those fields. In other words, to avoiding data duplication, separate those fields in another table with a reference to the original table and then use JOIN when you want to retrieve them.
For more detail see this
You cannot have a single #Entity annotated class for multiple tables.
However, you could use a class as the basis of other classes, by embedding the class into the other classes.
e.g. you could have:-
#Entity(tableName = "file")
data class File(
#PrimaryKey
#ColumnInfo(name = "path") var path: String,
#ColumnInfo(name = "date", index = true) var date: Long,
#ColumnInfo(name = "number") var num: Float = -1f
)
and the second class as :-
#Entity(tableName = "del_file",primaryKeys = ["path"], indices = [Index("date")])
data class delFile(
#Embedded
val file: File
)
However, you may need to be aware of what is and isn't applied to the class that Embeds another class.
You cannot use #ColumnInfo with an #Embedded (it is not necessarily a single column)
The #PrimaryKey is lost/dropped when Embedding thus why you would need to define the primary key in the #Entity annotation of the class that Embeds the other class (it is feasible to have multiple #Embeddeds and thus impossible to determine what the correct primary key should be (Room requires that a primary key is defined))
Likewise for the index on the date column and hence the indicies being defined in the #Entity of the delFile class that Embeds the other class.
However the name given in the #ColumnInfo of the first is used in the second (safe to propogate this).
As an example of the columns names being different than the field/variable name, if, for the first, you had:-
#Entity(tableName = "file")
data class File(
#PrimaryKey
#ColumnInfo(name = "mypath") var path: String,
#ColumnInfo(name = "mydate", index = true) var date: Long,
#ColumnInfo(name = "mynumber") var num: Float = -1f
)
Then as the column names are different to the field/variable names then you would have to use:-
#Entity(tableName = "del_file",primaryKeys = ["mypath"], indices = [Index("mydate")])
data class delFile(
#Embedded
val file: File
)
You would also have to be aware that, changes to the File class would also apply to the delFile class, which could be useful at times but potentially problematic at other times.
Changes to the second (delFile) class would not be applied to the first (File) class, so you would have the freedom to augment the second e.g.
#Entity(tableName = "del_file",primaryKeys = ["mypath"], indices = [Index("mydate")])
data class delFile(
#Embedded
val file: File,
val another_column: String
)
This would result in the del_file table having the additional another_column column.

Find entity with many-to-one relationship uses wrong id for query parameter

Look guys, I need some help with Room on Android.
In my project there are two entities, Item and Unit of Measure. The item can be in only one unit of measure but the unit of measure can be in several units.
As the project's interest is to deal with the Item, it would need to have a many-to-one relationship and as Room doesn’t deal with that, I chose to set up a many-to-many relationship and create just one Entity for cross-reference that present the meaning contrary.
The database is already started with 4 units of measurement, Kg, g, L and cm, so the total number of records is 4, the ids are 1, 2, 3 and 4 respectively.
When I insert the first item in the database with a Unit of Measure equal to Kg, it will have Id = 1 (because it was the first record) and when I search for it informing id 1, everything is ok, it works perfectly, it returns the item with the Unit of Measure Kg.
When I register the second item (so now this new item will have id 2) also with the Unit of Measurement equal to Kg there, problems begin to appear. When performing his search informing his id which is 2 he returns the item but as the Unit of Measure equal to g (gram) which is also code 2.
That is, when Room is going to mount the result of the item searched for, the Code of the Unit of Measure is being considered the same code as the Item.
Room doesn't seem to be looking at the id of the relationship I created for the Item with Unit of Measure, but I'm not doing it right, or I'm the one who forgot something.
ItemEntity
#Entity(
tableName = "item"
)
data class ItemEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "item_id")
var itemID: Long = 0,
#ColumnInfo(name = "description")
var description: String = "",
#ForeignKey(
entity = ComparationEntity::class,
parentColumns = ["comparation_id"],
childColumns = ["fk_comparation_id"],
onDelete = ForeignKey.CASCADE
)
#ColumnInfo(name = "fk_comparation_id")
var fkComparationID: Long = 0,
#ColumnInfo(name = "price")
private var price: Double = 0.0,
#ColumnInfo(name = "quantity")
var quantity: Double = 0.0,
#Ignore
private var priceUnit: Double = 0.0
) {
fun setPrice(price: Double) {
if (this.quantity.toFloat() != 0.00f && price.toFloat() != 0.00f) {
priceUnit = this.price.div(this.quantity)
}
}
fun getPrice() = price
fun getPriceUnit(): Double {
return priceUnit
}
}
UnitMeasureEntity
#Entity(
tableName = "unit_measure"
)
data class UnitMeasureEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "un_meas_id")
var unMeasID: Long = 0,
val description: String,
val acronym: String,
val symbol: String
)
RelationItemWithUnits
#Entity(tableName = "relation_item_whit_units", primaryKeys = ["item_id", "un_meas_id"])
data class RelationItemWithUnits(
#ColumnInfo(name = "item_id")
val itemID : Long,
#ColumnInfo(name = "un_meas_id")
val unMeasID : Long
)
ItemWithUnitMeasure
data class ItemWithUnitMeasure(
#Embedded
var item: ItemEntity,
#Relation(
entity = UnitMeasureEntity::class,
parentColumn = "item_id",
entityColumn = "un_meas_id",
associateBy = Junction(RelationItemWithUnits::class)
)
var unitMeasureWithMeasureType: UnitMeasureWithMeasureType
)
RelationsUnitsWithMeasuresType
#Entity(tableName = "relation_units_whit_measure_type", primaryKeys = ["un_meas_id", "measure_type_id"])
data class RelationsUnitsWithMeasuresType(
#ColumnInfo(name = "un_meas_id")
val unMeasID: Long,
#ColumnInfo(name = "measure_type_id")
val measureTypeID : Long
)
it would need to have a many-to-one relationship and as Room doesn’t deal with that, I chose to set up a many-to-many relationship and create just one Entity for cross-reference that present the meaning contrary
I think you've got into misconception there. Room's documentation really doesn't include any many-to-one relations. But many-to-one relation is the same as one-to-many in table's structure sense. Let's say you have table A and table B, and table A has many rows with the same id from the table B. As such tables hold one-to-many relation.
If you want to get all rows from A table and to attach there value from B table, you can use one-to-one relation from Room's documentation. Since one row from A has a relation only with one row from B.
If you want to get all rows from B table and to attach list of binded values from A table, you can use one-to-many relation. Since one row from B table has a relation with many rows from A.
So you don't need to use separate table and many-to-many relations here.
It would be much easier just to add unitMeasureId to ItemEntity table and then use one-to-one relation to get needed result.

Many to many android room ref extra variable

I have a Many to Many relationship set up on my Room database denoted by the following diagram:
eveything works great but I would like to make the following addition:
My question is how do I go about having RecipeWithIngredients get this unit:String variable from the RecipeIngredientRef Entity when a recipeWithIngredients is constructed on a query?
#Entity(indices = [Index(value = ["name"],unique = true)])
data class Recipe(
var name: String,
val image: String?
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "recipeID")
var ID: Long = 0
}
#Entity(indices = [Index(value = ["name"],unique = true)])
data class Ingredient(
val name: String,
val image: String?,
val amount: Int
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "ingredientID")
var ID: Long = 0
}
#Entity(primaryKeys = ["recipeID", "ingredientID"])
data class RecipeIngredientRef(
val recipeID: Long,
val ingredientID: Long,
val unit: String
)
data class RecipeWithIngredients(
#Embedded
val recipe: Recipe,
#Relation(
parentColumn = "recipeID",
entity = Ingredient::class,
entityColumn = "ingredientID",
associateBy = Junction(
value = RecipeIngredientRef::class,
parentColumn = "recipeID",
entityColumn = "ingredientID"
)
)
val ingredients: List<Ingredient>
)
As far as I know there is no built-in way using current many-to-many Relations in Room for that. So I just want to save your time for researches.
This RecipeIngredientRef table is used internally just for two other tables' join and for now adding there additional fields will not help to get these fields with #Relations/Junction mechanism.
You can try workarounds, but they are no so elegant and they needed to dig deeper into Room's result's processing:
Don't use #Relation/Junction mechanism for RecipeWithIngridients at all, write your own query with two JOINs (since you should get data from 3 tables). As a result you'll get some raw set of records and then you should use loops and methods of Kotlin collections to turn result into needed form.
Use #Relation/Junction mechanism, but after getting result - make additional processing of result - make one more query to RecipeIngredientRef table and set missing unit value manually (again with loops and methods of Kotlin collections).

How to store a list of integers in Room database so that it can be easily queried later?

I'm storing podcast data in a Room database, where each podcast has a List<Int> called genreIds. I'd like to be able to store this in such a way that I can easily query it later by doing something like SELECT * FROM podcasts WHERE genreIds CONTAINS :genre, or whatever the command would be.
So what is the best way to store that list of Ints so that it can be easily queried later, and how would I do that using Room? I've used TypeConverters before, but that converts it to a string, which is difficult to query, so I'd like to be able to link it to another table or something that can be easily queried, I'm just not sure how to do that.
Thanks in advance.
The data stored on a the db with Room, depends on the data class you use. If you specify a data class with an Int member, that will be an Int on the db.
Example:
data class TrackPlayedDB (
#PrimaryKey(autoGenerate = true)
val _id: Int = 0,
val timesPlayed: Int,
val artist: String,
val trackTitle: String,
val playDateTime: LocalDateTime
)
here timesPlayed will be an Int on the DB (as _id). You'll specify your data classes like the following, this will build the corresponding tables.
#Database(entities = [TrackPlayedDB::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class MyRoomDatabase : BaseRoomDatabase() {
Edit: Following author's comment, I stand corrected i didn't get the question right.
Author actually asks how to store a List<Int> as field on a table. There are 2 solutions to do that: one, as Author suggests, is to store the List as String and use Like keyword to write queries with a clause like the following:
SELECT * FROM mytable
WHERE column1 LIKE '%word1%'
OR column1 LIKE '%word2%'
OR column1 LIKE '%word3%'
as a simple search on SO would have shown: SQL SELECT WHERE field contains words
The Author says he used TypeConverters so i'll skip how to convert a List<Int> into a string
The other solution to this problem is to realise that nothing was understood about the theory of Transactional Databases. In fact, when you have a many-to-many relationship, as in the case of podcast and genre, theory dictates that you build a table that links the ids of podcasts and the ids of genres, as it is explained here: https://dzone.com/articles/how-to-handle-a-many-to-many-relationship-in-datab
and other countless books, videos and blogs.
This benefits the db with added clarity, performance and scalability.
Bottom line, Author's db design is wrong.
I found [this article on Medium][1] that I found very helpful. What I'm trying to do is a many to many relationship, which in this case would be done something like the following:
Podcast class:
#Entity(tableName = "podcasts")
data class Podcast(
#PrimaryKey
#ColumnInfo(name = "podcast_id")
val id: String,
// other fields
}
Genre class:
#Entity(tableName = "genres")
data class Genre (
#PrimaryKey
#ColumnInfo(name = "genre_id")
val id: Int,
val name: String,
val parent_id: Int
)
PodcastDetails class:
data class PodcastDetails (
#Embedded
val podcast: Podcast,
#Relation(
parentColumn = "podcast_id",
entityColumn = "genre_id",
associateBy = Junction(PodcastGenreCrossRef::class)
)
val genres: List<Genre>
)
PodcastGenreCrossRef:
#Entity(primaryKeys = ["podcast_id", "genre_id"])
data class PodcastGenreCrossRef (
val podcast_id: Int,
val genre_id: Int
)
And access it in the DAO like this:
#Transaction
#Query(SELECT * FROM podcasts)
fun getPodcastsWithGenre() : List<PodcastDetails>
[1]: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542

Categories

Resources