I'm trying to save objects in the room database.
My profile object class that I want to save in the database:
class Profile(
val exampleString : String = "Example String",
val exampleStringTwo : String = "Example String Two",
val subObjectsList : ArrayList<SubObjecst> = ArrayList()
)
Profile object include Arraylist of Subobjects with this structure:
class SubObjects(
val exampleString : String = "Example 1",
val exampleStringTwo : String = "Example 2",
val exampleStringThree: String = "Example 3"
)
data class ProfileEntity(
#PrimaryKey(autoGenerate = true) val id: Long?,
#ColumnInfo(name = "profile") var profille: Profile
) : Parcelable
How to save Profile object in the room database? I'm stuck on the Type converter for this object.
Using a TypeConverter
Type converters are really for converting simple types, say a date or enum value, however it could be used for this case. You would need to have some way converting your SubObject to a String, probably with some sort of delimiter that you are confident doesn't exist in your object fields already. So something like:
fun SubObject.toString() = "$exampleString##$exampleString2##$exampleString3"
However this doesn't really leverage what SQL is for, which is column-based data.
Using Relationships
This is probably the method you want. By having a separate Entity for your Profile and SubObject models, you can then query the database to return the relationship between your Profile and SubObject, specifically in a one-to-many relationship. In Room, this is implemented using the #Embedded and #Relation annotations. I would recommend taking a look at the Android Devleopers documentation here but for your example you want something like this:
class ResolvedProfile(
#Embedded
val profile: Profile,
#Relation(parentColumn = "profileId", entityColumn = "subObjectProfileId")
val subObjects: List<SubObject>
)
Related
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.
I have two models
Types Model
#Entity(tablename="tbl_types")
data class Types(
#PrimaryKey(autoGenerate = true)
val typesId:Int,
val name: String?,
val posts:List<Post>?))
PostModel
#Entity(tablename="tbl_post")
data class Post(
#PrimaryKey(autoGenerate = true)
val postId:Int,
val name: String?,
val details:String?))
A post can be a part of different types so when I change the post's data like name or details I want to update all the types that have this specific post in its posts column. How do I do that?. Do I have to update the type table manually? I don't quite know how the foreign key works and if it will let me update the item on the list or not.
Ok Room support Embedded objects in an entity so lets make Post embedded in Type like this:
#Entity(tablename="tbl_types")
data class Types(
#PrimaryKey(autoGenerate = true)
val typesId:Int,
val name: String?,
#Embedded val posts:List<Post>?))
now for updating the post you easily just change the desired post in the list, in the Type Model and you just need to say :
#Update
fun updateType(type: Types)
and that's the power of Room for updating a row easily.
Hope it Helps.
Not containing Post in Type table. Create third table to save the relateion of post and type.
#Entity(tablename="tbl_types")
data class Types(
#PrimaryKey(autoGenerate = true)
val typesId:Int,
val name: String?)
#Entity(tablename="tbl_post")
data class Post(
#PrimaryKey(autoGenerate = true)
val postId:Int,
val name: String?,
val details:String?))
#Entity(tablename="type_post_relation", primaryKeys =
["postId","typeId"])
data class PostTypeRelation(
val postId:Int,
val typeId: Int,
)
...
#Query("UPDATE Tour SET typeId = :toTypeId WHERE postId = :postId and typeId = : fromTypeId")
fun updatePostType(postId: Int, fromTypeId: Int, toTypeId: Int)
The relation table uses postId and typeId to be primary key. When changing post type, just update relation table with a postId and typeId. And if you want to query all posts in one specific type, you can use sql to combine query, so querying post's types.
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
Here is my Table:
#Entity(tableName = "user_data")
data class UserData(
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "matched_users") var matchedUsers: ArrayList<String>
)
I want a Boolean query to see if matchedUsers contains the passed-in string:
#Dao
interface UserDataDao {
#Query("SELECT * FROM user_data WHERE :matchId IN matched_users")
fun matchedBefore(matchId: String): Boolean
}
Obviously my query is wrong, but how can I achieve this?
PS: If ArrayList is not possible, can I use List<> or Set<>?
Instead of making matchedUsers is autogenerated , take another property in UserData class , id an Int which will be #PrimaryKey and auto generated .
You can not directly store an arraylist in room , you need to use a TypeConverter .
See my answer on how to use type converter .
Your complete arraylist of string will be stored as single string after using type converter . You can then write a query to check if the string contains a substring .
I’ve reviewed a lot of Stack answers related to this and I’m still not getting it. I have an #Embedded class, but I’d rather this be a Boolean array with three elements. Would someone please hit me with a clue stick and help me redesign this entity to handle this or provide the TypeConverter I need? I’d rather not use a JSON/GSON converter if I can avoid it.
data class Bools (val a: Boolean = true,
val b: Boolean = true,
val c: Boolean = false)
#Entity(tableName = "people_table")
data class Person (#ColumnInfo(name = "first_name") val firstName: String,
#ColumnInfo(name = "last_name") val lastName: String,
#Embedded
val bool: Bools
){
#PrimaryKey(autoGenerate = true)
var id: Int = 0
}
Thank you.
In general, I do not recommend that Room entities, Retrofit responses, and similar things be considered your in-memory data model. They are data transfer objects, as they are subject to limitations that your UI and in-app logic should not need to deal with. In the case of something like Retrofit, the way the data is organized and delivered by the server may bear little resemblance to how you want to work with the data in the app. In your case, it's that you want three distinct columns, which means three distinct Kotlin properties, whether in the entity or in an #Embedded object, as you have it.
A typical approach is to have a PersonDTO or PersonEntity or something be what Room uses, which you convert to/from Person objects that have your desired structure:
data class Person (val firstName: String,
val lastName: String,
val boolsheet: BooleanArray)
#Entity(tableName = "people_table")
data class PersonEntity (#ColumnInfo(name = "first_name") val firstName: String,
#ColumnInfo(name = "last_name") val lastName: String,
val a: Boolean = true,
val b: Boolean = true,
val c: Boolean = false
){
constructor(somebody: Person): this(
somebody.firstName,
somebody.lastName,
somebody.boolsheet[0],
somebody.boolsheet[1],
somebody.boolsheet[2]
)
#PrimaryKey(autoGenerate = true)
var id: Int = 0
fun toPerson(): Person = Person(firstName, lastName, booleanArrayOf(a, b, c))
}
Now, Person and everything that deals with it knows nothing about Room, and you have the API that you want. PersonEntity would be used by your repository, hiding the details. And, if someday you need to have the repository also talk to a server, the repository can normalize between Person and the representation that you need for your Web service API.
If you don't like that, stick with your Person and #Embedded, and add a val boolsheet = booleanArrayOf(bools.a, bools.b, bools.c) to it, to get your Boolean values in a iterable structure.