Can't find proper relation and architecture saving data in ROOM - android

I have a user table like this
#Entity(tableName = "users")
data class Users(
#ColumnInfo(name = "id") #PrimaryKey val id: String,
#ColumnInfo(name = "fullName") val fullName: String
...
)
Now I have event information from REST
{
"id": 1,
"eventName": "Event name",
...
"participantList": [
{
"userId": 1,
"status": "going"
},
{
"userId": 2,
"status": "interested"
}
]
}
How can I save this in Room and what should be the entities and relations so that I can have a Event enitity with list of users and each users status. for example
data class Event(
val id:Int,
val eventName: String,
val participantList: List<UserWithStatus>
)
data class UserWithStatus(
val user:User,
val status:String
)

It seems you should use nested relationships in your case.
Try next schema (you can add Foreign Keys if you wish):
Entities
#Entity(tableName = "user")
data class User(
#PrimaryKey var id: Int,
var fullName: String
)
#Entity(tableName = "event")
data class Event(
#PrimaryKey val id:Int,
val eventName: String
)
#Entity(tableName = "event_details")
data class EventDetails(
#PrimaryKey val id:Int,
val eventId: Int, // make it Foreign Key if you want
val userId: Int, // make it Foreign Key if you want
val status:String
)
Auxiliary classes
data class UserWithStatus(
val userId:Int,
val status: String,
#Relation(
entity = User::class,
parentColumn = "userId",
entityColumn = "id"
)
val user: User
)
data class EventWithUser(
#Embedded
val event: Event,
#Relation(
entity = EventDetails::class,
parentColumn = "id",
entityColumn = "eventId"
)
val participantList: List<UserWithStatus>
)
DAO
#Query("select * from event")
fun getEventsWithUsers(): List<EventWithUser>

Related

ROOM database entity modeling

There must be a better way of doing this. I want to create a database table with all my clothing and have subcategories of clothing, like outerwear, dresses, shoes, etc. They all will have the same attributes (Id, name, image, about, price). Couldn't I create one table? I believe this is a One-to-Many relationship.
#Serializable
#Entity(tableName = CLOTHING_DATABASE_TABLE)
data class Clothing(
#PrimaryKey(autoGenerate = false)
val id: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = POPULAR_DATABASE_TABLE)
data class Popular(
#PrimaryKey(autoGenerate = false)
val popularId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = OUTERWEAR_DATABASE_TABLE)
data class Outerwear(
#PrimaryKey(autoGenerate = false)
val outerwearId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = TOPS_DATABASE_TABLE)
data class Tops(
#PrimaryKey(autoGenerate = false)
val topsId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = SWIMWEAR_DATABASE_TABLE)
data class Swimwear(
#PrimaryKey(autoGenerate = false)
val swimwearId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = SHOES_DATABASE_TABLE)
data class Shoes(
#PrimaryKey(autoGenerate = false)
val shoesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = BUNDLES_DATABASE_TABLE)
data class Bundles(
#PrimaryKey(autoGenerate = false)
val bundlesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = DRESSES_DATABASE_TABLE)
data class Dresses(
#PrimaryKey(autoGenerate = false)
val dressesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = PAJAMAS_DATABASE_TABLE)
data class Pajamas(
#PrimaryKey(autoGenerate = false)
val pajamasId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = ACCESSORIES_DATABASE_TABLE)
data class Accessories(
#PrimaryKey(autoGenerate = false)
val accessoriesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
You would typically have either 2 or 3 tables (3 for a many-many i.e. an item of clothing could have multiple sub-categories).
For one-many have a clothing table which has a column for the sub-category that references(relates) to the single sub-category and a sub-category table that is referenced according to a unique column (the primary key).
For the many-many you have the clothing table (without the column to reference the single sub-category), the sub-category table and then a third table that has two columns, one for the reference to the clothing and the other for the reference to the sub-category with the primary key being a composite of both.
So you could have:-
#Entity(tableName = CLOTHING_DATABASE_TABLE)
data class Clothing(
#PrimaryKey(autoGenerate = false)
val id: Long, /* should really be Long as */
val subCategoryReference: Long, /*<<<<< ADDED for the 1 subcategory */
val name: String,
val image: String,
val about: String,
val price: String
)
and :-
#Entity(tableName = SUBCATEGORY_DATABASE_TABLE)
data class SubCategory(
#PrimaryKey
val id: Long?,
val subCategoryName: String
)
to enforce referential integrity you could add a foreign key constraint to the subCategoryReference column of the clothing table.
If you wanted a many-many, allowing a clothing to have multiple sub-categories then you could have the third table as :-
#Entity(
tableName = CLOTHING_SUBCATEGORY_MAP_DATABASE_TABLE,
primaryKeys = ["clothingMap","subcategoryMap"],
)
data class ClothingSubCategoryMap(
val clothingMap: Long,
#ColumnInfo(index = true)
val subcategoryMap: Long
)
Of course you could have a single clothing table and just have a column for the sub-category. However this would be considered as not being normalised as you would be duplicating the sub-category throughout.
Example 1-many (i.e. using the 2 tables Clothing and SubCategory)
As you would very likely want to retrieve clothing along with it's sub-category then you would have a POJO that uses the #Embedded and #Relation annotations e.g.
data class ClothingWithSingleSubCategory (
#Embedded
val clothing: Clothing,
#Relation(
entity = SubCategory::class,
parentColumn = "subCategoryReference",
entityColumn = "id"
)
val subCategory: SubCategory
)
You could then have the following as an #Dao annotated class :-
#Dao
interface AllDao {
#Insert(onConflict = IGNORE)
fun insert(clothing: Clothing): Long
#Insert(onConflict = IGNORE)
fun insert(subCategory: SubCategory): Long
#Transaction
#Query("SELECT * FROM clothing")
fun getAllClothingWithSubCategory(): List<ClothingWithSingleSubCategory>
}
With a suitable #Database annotated class you could then have something like the following in an activity:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val sc_popular = dao.insert(SubCategory(null,"Popular"))
val sc_outerwear = dao.insert(SubCategory(null,"OuterWear"))
val sc_tops = dao.insert(SubCategory(null,"Tops"))
val sc_swimwear = dao.insert(SubCategory(100,"Swimwear"))
val sc_shoes = dao.insert(SubCategory(null,"Shoes"))
val sc_dresses = dao.insert(SubCategory(null,"Dresses"))
val sc_pyjamas = dao.insert(SubCategory(null,"Pyjamas"))
dao.insert(Clothing(100200300400,sc_popular,"Jeans","jeans_image","blah","100.55"))
dao.insert(Clothing(100200300500,sc_outerwear,"Anorak","anorak_image","blah","214.55"))
for (cwsc: ClothingWithSingleSubCategory in dao.getAllClothingWithSubCategory()) {
Log.d("DBINFO","Name = ${cwsc.clothing.name} Price is ${cwsc.clothing.price} Sub-Category is ${cwsc.subCategory.subCategoryName}")
}
}
}
When run the log would then include:-
D/DBINFO: Name = Jeans Price is 100.55 Sub-Category is Popular
D/DBINFO: Name = Anorak Price is 214.55 Sub-Category is OuterWear
Example many-many
Like the 1-many you will want a POJO BUT one that has a List of sub-categories obtained via the mapping table. This uses the #Embeded annotation and the #Relation annotation but extended to include the associateBy to inform Room about the intermediate table. So you could have:-
data class ClothingWithListOfSubCategories(
#Embedded
val clothing: Clothing,
#Relation(
entity = SubCategory::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = ClothingSubCategoryMap::class,
parentColumn = "clothingMap",
entityColumn = "subcategoryMap"
)
)
val subCategories: List<SubCategory>
)
The you could have the following in an #Dao annotated class:-
/* ADDED for many-many */
#Insert(onConflict = IGNORE)
fun insert(clothingSubCategoryMap: ClothingSubCategoryMap): Long
#Transaction
#Query("SELECT * FROM clothing")
fun getAllClothingWithSubCategories(): List<ClothingWithListOfSubCategories>
and if the activity were extended to include :-
/* Added for many-many */
/* Note utilises clothing and sub-categories above */
dao.insert(ClothingSubCategoryMap(jeans,sc_popular))
dao.insert(ClothingSubCategoryMap(jeans,sc_swimwear))
dao.insert(ClothingSubCategoryMap(jeans,sc_shoes))
dao.insert(ClothingSubCategoryMap(anorak,sc_popular))
dao.insert(ClothingSubCategoryMap(anorak,sc_outerwear))
for(cwlsc: ClothingWithListOfSubCategories in dao.getAllClothingWithSubCategories()) {
Log.d("DBINFO","Name = ${cwlsc.clothing.name} Price is ${cwlsc.clothing.price} it is in ${cwlsc.subCategories.size} sub-categories. They are:-")
for(sc: SubCategory in cwlsc.subCategories) {
Log.d("DBINFO","\t${sc.subCategoryName}")
}
}
The the log would also include :-
D/DBINFO: Name = Jeans Price is 100.55 it is in 3 sub-categories. They are:-
D/DBINFO: Popular
D/DBINFO: Swimwear
D/DBINFO: Shoes
D/DBINFO: Name = Anorak Price is 214.55 it is in 2 sub-categories. They are:-
D/DBINFO: Popular
D/DBINFO: OuterWear

Room one-to-many, to many

I am little a bit confused on how i should set up a data class consisting of two lists.
I have a data class looking like this
#Entity(tableName = "recipe")
data class Recipe(
#PrimaryKey(autoGenerate = false)
val recipeName: String,
//val keyIngrediens: List<KeyIngredient>,
//val steps: List<steps>
So for KeyIngrediens i have set it up like this and it works perfectly without problems
#Entity
data class KeyIngredients(
#PrimaryKey(autoGenerate = false)
val title: String,
val image: String,
val undertitle: Int,
val recipeName: String
)
data class RecipeWithkeyIngredients(
#Embedded val recipe: Recipe,
#Relation(
parentColumn = "recipeName",
entityColumn = "recipeName"
)
val keyIngredients: List<KeyIngredients>
)
So basically, i got it to work with one list and one object, but Im a bit confused as how i should handle it when i have two lists in a single object. Right now I'm calling RecipeWithKeyIngredients which returns the recipe object with the list of key ingredients, but i don't know how to make it also contain the steps list.
In short it should be like RecipeWithkeyIngredientsANDsteps if that is possible.
This is not enough for recipes. A recipe contains many ingredients, an ingredient belongs to many recipes. So it should be handled with many-to-many relationship.
#Entity
data class Recipe(
#PrimaryKey
val recipeId: Long,
val name: String
)
#Entity
data class KeyIngredient(
#PrimaryKey
val keyIngredientId: Long,
val title: String,
val image: String,
val undertitle: Int,
)
#Entity
data class Step(
#PrimaryKey
val stepId: Long,
val instruction: String,
val stepRecipeId: Long
)
#Entity(
primaryKeys = ["recipeId", "keyIngredientId"]
)
data class RecipeKeyIngredient(
val recipeId: Long,
val keyIngredientId: Long
)
Get the list of ingredients of a recipe
data class RecipeWithIngredients (
#Embedded
val recipe: Recipe,
#Relation(
parentColumn = "recipeId",
entity = KeyIngredient::class,
entityColumn = "ingredientId",
associateBy = Junction(
value = RecipeKeyIngredient::class,
parentColumn = "recipeId",
entityColumn = "keyIngredientId"
)
)
val keyIngredients: List<KeyIngredient>
)
Get the list of recipes with the same ingredient
data class IngredientWithRecipes(
#Embedded
val ingredient: Ingredient,
#Relation(
parentColumn = "ingredientId",
entity = Recipe::class,
entityColumn = "recipeId",
associateBy = Junction(
value = RecipeKeyIngredient::class,
parentColumn = "ingredientId",
entityColumn = "recipeId"
)
)
val recipes: List<Recipe>
)
Now you can query database for the result as:
#Dao
interface RecipeKeyIngredientDao {
#Query("SELECT * FROM Recipe")
fun getRecipeWithIngredients(): LiveData<List<RecipeWithIngredients>>
#Query("SELECT * FROM KeyIngredient")
fun getIngredientWithRecipes(): LiveData<List<IngredientWithRecipes>>
}
For the other part of the question, a recipe typically contains specific set of steps (instructions?) and to define the relationship between recipe and steps you will need nested relationship which defines one-to-many relationship between recipe and steps, and get the recipe from already modelled IngredientsOfRecipe instead of Recipe table which will give recipe with ingredients. Model the same as
data class RecipeWithIngredientsAndSteps(
#Embedded val recipe: IngredientsOfRecipe
#Relation(
entity = Step::class,
parentColumn = "recipeId",
entityColumn = "stepRecipeId"
)
val steps: List<Step>
)
Query using single transaction as follows
#Transaction
#Query("SELECT * FROM Recipe")
fun getRecipeWithIngredientsAndSteps(): List<RecipeWithIngredientsAndSteps>
Please refer to this
Assuming your Steps data class as follows:
#Entity
data class Steps(
#PrimaryKay
val stepName: String,
val duration: Int,
val recipeName: String,
val rank: Int
)
data class RecipeWithKeyIngredientsAndSteps(
#Embedded recipe: Recipe,
#Relation(
parentColumn = "recipeName",
entityColumn = "recipeName"
)
val keyIngredients: List<KeyIngredients>,
#Relation(
parentColumn = "recipeName",
entityColumn = "recipeName"
)
val steps: List<Steps>
)
At the end it's still a One-to-N Relationship

Moshi and room - mapping relationships

I have that Json that I would like to map with Moshi and store with Room
{
"name": "My Group",
"members": [
{
"id": "119075",
"invitedUser": {
"id": 97375,
"email": "xxx#gmail.com"
},
"inviting_user": {
"id": 323915,
"email": "yyy#gmail.com"
}
},
{
"id": "395387",
"invitedUser": {
"id": 323915,
"email": "aaa#gmail.com"
},
"inviting_user": {
"id": 323915,
"email": "bbb",
}
}
]
}
I prepared my models
#Entity(tableName = "groups")
data class Group(
#PrimaryKey
val id: Long,
val members: List<Member>
)
#Entity(tableName = "members")
data class Member(
#PrimaryKey
val id: Long,
#Json(name = "invited_user")
#ColumnInfo(name = "invited_user")
val invitedUser: User,
#Json(name = "inviting_user")
#ColumnInfo(name = "inviting_user")
val invitingUser: User
)
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val email: String
)
And currently, I have error: Cannot figure out how to save this field into database.
I read this https://developer.android.com/training/data-storage/room/relationships. However, if I will model relationships like in documentation I don't know how to let Moshi map the relations? Have you found the simplest solution for that problem?
You have 2 options in my opinion.
You split the group and users in to individual tables and insert them separately.
You use TypeConverters to store the members as a field of group.
Your implementation is going to be dependent on your use-case.
Finally, I stored it by using TypeConverters
private val membersType = Types.newParameterizedType(List::class.java, Member::class.java)
private val membersAdapter = moshi.adapter<List<Member>>(membersType)
#TypeConverter
fun stringToMembers(string: String): List<Member> {
return membersAdapter.fromJson(string).orEmpty()
}
#TypeConverter
fun membersToString(members: List<Member>): String {
return membersAdapter.toJson(members)
}
And that are my models
#TypeConverters(Converters::class)
#Entity(tableName = "groups")
data class Group(
#PrimaryKey
val id: Long,
val name: String
) {
companion object {
data class Member(
val id: Long,
val invitedUser: User,
val invitingUser: User
)
data class User(
val id: Long,
val email: String
)
}
}
Does it look good for you?
Probably cleaner would be to have only ids and store somewhere else users, but I like that this solution is so simple.
You use TypeConverters to store the members as a field of group.
I believe this is the Implementation you need.
open class UserRequestConverter {
private val moshi = Moshi.Builder().build()
#TypeConverter
fun fromJson(string: String): User? {
if (TextUtils.isEmpty(string))
return null
val jsonAdapter = moshi.adapter(User::class.java)
return jsonAdapter.fromJson(string)
}
#TypeConverter
fun toJson(user: User): String {
val jsonAdapter = moshi.adapter(User::class.java)
return jsonAdapter.toJson(user)
}
}
#Entity(tableName = "members")
data class Member(
#PrimaryKey
val id: Long,
#Json(name = "invited_user")
#ColumnInfo(name = "invited_user")
#TypeConverters(UserRequestConverter::class)
val invitedUser: User,
#Json(name = "inviting_user")
#ColumnInfo(name = "inviting_user")
#TypeConverters(UserRequestConverter::class)
val invitingUser: User
)

Android Room - Many-to-many relationship not returning relation entities

I've followed the documentation provided on android developer guides and a Medium article.
I'm trying to return a playlist of songs, but want a list of entities and not just the IDs. Following the above links I have this.
My Entities:
#Entity
data class MediaEntity(
#PrimaryKey val identifier: String,
val imageUrl: String,
val title: String,
val description: String,
val media: String,
val duration: Double,
val progress: Int,
val listenedToCompletion: Boolean
)
#Entity
data class PlaylistEntity(
#PrimaryKey val playlistId: String,
val playlistName: String,
val playlistDescription: String = "",
val currentPosition: Int = 0
)
#Entity(primaryKeys = ["playlistId", "identifier"])
data class PlaylistMediaLinkEntity(
val playlistId: String,
val identifier: String
)
My DAO for the link table is as follows:
#Dao
interface PlaylistMediaLinkDao {
#Transaction
#Query("SELECT * FROM PLAYLISTENTITY")
fun getPlaylistWithMediaItems(): List<MediaInPlaylist>
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(playlistMediaLinkEntity: PlaylistMediaLinkEntity)
}
And then my media in playlist object:
class MediaInPlaylist() {
#Embedded
lateinit var playlist: PlaylistEntity
#Relation(
parentColumn = "playlistId",
entity = MediaEntity::class,
entityColumn = "identifier",
associateBy = Junction(
value = PlaylistMediaLinkEntity::class,
parentColumn = "playlistId",
entityColumn = "identifier"
)
)
lateinit var mediaEntities: List<MediaEntity>
}
I can confirm my PlaylistMediaLinkEntity is populated with playlist-media id pairs, but when calling getAllPlaylistsWithMediaItems, the MediaInPlaylist object is returned with the Playlist data, but the list of mediaEntries is empty.
Have I left something out or where am I going wrong? What I've done matches most examples online.

Room : how to implement several one-to-one relations?

I can find several examples and a good documentation on how to implement a one-to-one relation using Room, but I cannot find any documentation on how to implement several one-to-one relations.
Here an example based on this article.
If 1 dog has 1 owner, I can create a Dog entity:
#Entity
data class Dog(
#PrimaryKey val dogId: Long,
val dogOwnerId: Long,
val name: String,
val cuteness: Int,
val barkVolume: Int,
val breed: String
)
Then I can create a Owner entity:
#Entity
data class Owner(#PrimaryKey val ownerId: Long, val name: String)
Now, I can create a DogAndOwner data class in order to retrieve a dog and its owner with Room:
data class DogAndOwner(
#Embedded val owner: Owner,
#Relation(
parentColumn = "ownerId",
entityColumn = "dogOwnerId"
)
val dog: Dog
)
and the request:
#Transaction
#Query("SELECT * FROM Owner")
fun getDogsAndOwners(): List<DogAndOwner>
Now, I would like to add another one-to-one relation to my dog, for example a home.
I can create the Home entity:
#Entity
data class Home(#PrimaryKey val homeId: Long, val address: String)
and I can update my Dog entity with the dogHome attribute:
#Entity
data class Dog(
#PrimaryKey val dogId: Long,
val dogOwnerId: Long,
val dogHomeId: Long,
val name: String,
val cuteness: Int,
val barkVolume: Int,
val breed: String
)
Now, the question is, how to create a DogAndOwnerAndHome data class? I would like to write something like that:
data class DogAndOwner(
#Embedded val owner: Owner,
#Embedded val home: Home,
#Relation(
parentColumn = "ownerId",
entityColumn = "dogOwnerId"
)
#Relation(
parentColumn = "homeId",
entityColumn = "dogHomeId"
)
val dog: Dog
)
but... the Relation annotation is not repeatable so I cannot. It is possible to retrieve directly a dog, its owner and its home with Room ?
Thank you in advance for your help.
I believe that you can use :-
data class DogAndOwnerAndHome (
#Embedded
val dog: Dog,
#Relation(entity = Owner::class,parentColumn = "dogOwnerId", entityColumn = "ownerId" )
val owner: Owner,
#Relation(entity = Home::class,parentColumn = "dogHomeId", entityColumn = "homeId" )
val home: Home
)
You may wish to change the Dog and Owner entities to ensure that column names are unique e.g. :-
#Entity
data class Dog(
#PrimaryKey val dogId: Long,
val dogOwnerId: Long,
val dogHomeId: Long,
val dogname: String,
val dogcuteness: Int,
val dogbarkVolume: Int,
val dogbreed: String
)
and :-
#Entity
data class Owner(#PrimaryKey val ownerId: Long, val ownername: String)
You can then use (as an example) :-
#Transaction
#Query("SELECT * FROM Dog")
fun getAllDogsWithOwnerAndHome() : List<DogAndOwnerAndHome>
You will need one of the later versions of the room libraries e.g.
kapt 'androidx.room:room-compiler:2.2.3'
implementation 'androidx.room:room-runtime:2.2.3'
Demo/Test
Using :-
val database = Room.databaseBuilder(this,AppDatabase::class.java,"petdb")
.allowMainThreadQueries()
.build()
val homeid = database.allDao().insertHome(Home(0,"Myhouse"))
val ownerid = database.allDao().insertOwner(Owner(0,"Me"))
val dogid = database.allDao().insertDog(Dog(0,ownerid,homeid,"Woof",1,0,"terrier"))
val alldogswithownerandwithhome = database.allDao().getAllDogsWithOwnerAndHome()
for (dwoh: DogAndOwnerAndHome in alldogswithownerandwithhome) {
Log.d("DOGDETAILS","Dog name is " + dwoh.dog.dogname + " Owned By " + dwoh.owner.ownername + " Lives at " + dwoh.home.address)
}
Testing the above results in :-
D/DOGDETAILS: Dog name is Woof Owned By Me Lives at Myhouse
It sound like you need a composite key to ensure 1 to 1 relationships:
#Entity(primaryKeys = ["dog","owner"])
data class DogAndOwner(
val owner: Owner,
val home: Home,
val dog: Dog
)

Categories

Resources