Room
I have a relationship like this
puzzle -> has one-> dialogue -> has many -> dialogue lines
I followed the instructions on
https://developer.android.com/training/data-storage/room/relationships#kotlin
but still cant get it to work
These are the error that seem to be related:
error: Cannot find the child entity column puzzleDialogueId in com.example.puzzleherexamenandroid.data.room.databaseModels.DialogueWithLinesDatabase.
Tried the following constructors but they failed to match:
PuzzleWithDialogueDatabase(com.example.puzzleherexamenandroid.data.room.databaseModels.PuzzleDatabase,com.example.puzzleherexamenandroid.data.room.databaseModels.DialogueWithLinesDatabase) -> [param:puzzle -> matched field:puzzle, param:dialogueWithLines -> matched field:unmatched]C:\Users\Jasper\StudioProjects\PuzzleHerexamenAndroid\app\build\tmp\kapt3\stubs\debug\com\example\puzzleherexamenandroid\data\room\databaseModels\PuzzleWithDialogueDatabase.java:9: error: Cannot find setter for field.
PuzzleDatabaseDao.java:12: error: Type of the parameter must be a class annotated with #Entity or a collection/array of it.
java.util.List<com.example.puzzleherexamenandroid.data.room.databaseModels.PuzzleWithDialogueDatabase> puzzle);
these are my entities
#Entity(tableName = "puzzle_table")
#Parcelize
data class PuzzleDatabase (
#PrimaryKey
val puzzleId: Int,
val title: String,
val prompt: String,
val answer: String
): Parcelable
#Entity(tableName = "dialogue_table")
#Parcelize
data class DialogueDatabase (
#PrimaryKey
val dialogueId: Int,
val prompt: String,
val char1avatar: String,
val char2avatar: String,
val puzzleDialogueId : Int
): Parcelable
#Entity(tableName = "dialogueLine_table")
#Parcelize
data class DialogueLineDatabase (
#PrimaryKey
val dialogueLineId: Int,
val line: String,
val speaking: Int,
val dialogueForeignkeyId: Int
): Parcelable
these are the classes for the relationship
data class PuzzleWithDialogueDatabase(
#Embedded val puzzle : PuzzleDatabase,
#Relation(
entity = DialogueWithLinesDatabase::class,
parentColumn = "puzzleId",
entityColumn = "puzzleDialogueId"
)
val dialogueWithLines: DialogueWithLinesDatabase
)
data class DialogueWithLinesDatabase(
#Embedded val dialogue:DialogueDatabase,
#Relation(
parentColumn = "dialogueId",
entityColumn = "dialogueForeignkeyId"
)
val dialogueLines: List<DialogueLineDatabase>
)
And this is my doa
#Dao
interface PuzzleDatabaseDao {
#Transaction
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertAll(puzzle: List<PuzzleWithDialogueDatabase>)
#Transaction
#Query("SELECT * from puzzle_table ORDER BY puzzleId DESC")
fun getAllPuzzles(): LiveData<List<PuzzleWithDialogueDatabase>>
}
I guess you should change entity DialogueWithLinesDatabase with DialogueDatabase inside your PuzzleWithDialogueDatabase class' Releation definition:
data class PuzzleWithDialogueDatabase(
#Embedded val puzzle : PuzzleDatabase,
#Relation(
entity = DialogueDatabase::class, // <- changed
parentColumn = "puzzleId",
entityColumn = "puzzleDialogueId"
)
val dialogueWithLines: DialogueWithLinesDatabase
)
The entity property in #Relation should mention the database entity and not the relation-class. Try:
data class PuzzleWithDialogueDatabase(
#Embedded val puzzle : PuzzleDatabase,
#Relation(
entity = DialogueDatabase::class,
parentColumn = "puzzleId",
entityColumn = "puzzleDialogueId"
)
val dialogueWithLines: DialogueWithLinesDatabase
)
Related
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
I have a query with a complex relation.
data class GameWithHouseworkResult(
#Embedded val game:GameClass,
#Relation(
entity = GameHouseworkResult::class,
parentColumn = "gameId",
entityColumn = "houseworkId" ,
associateBy = Junction(value=GameHouseworkResult::class)
)
val houseworkAndResult: List<HouseworkAndResult>
)
I have 3 entities and one additional class
#Entity
class GameClass(
#PrimaryKey(autoGenerate = true)
var gameId: Int = 0,
val mainPlayer:String,
var secondPlayer:String=""
}
#Entity
data class HouseworkClass(
#PrimaryKey
val houseworkId: Int,
val name:String,
val score:Int
) {
}
#Entity(primaryKeys = ["gameId", "houseworkId"])
class GameHouseworkResult(
var gameId: Long,
val houseworkId:Long,
val userToken:String,
val isDone:Boolean?= null) {
}
data class HouseworkAndResult(
#Embedded val gameHouseworkResult:GameHouseworkResult,
#Relation(
parentColumn = "houseworkId",
entityColumn = "houseworkId"
)
val housework: HouseworkClass
)
GameHouseworkResult is a binder class, it contains the following data
#Transaction
#Query("SELECT * FROM GameClass where gameId=:id")
suspend fun getGameWithHouseworkResult(id:Long): GameWithHouseworkResult
when I send a request with gameId = 26, I should get a list(houseworkAndResult) with five elements, but I get a list of 10 elements, and each one says that gameId = 26.
Most likely this is due to the same houseworkId, but I cannot understand where I went wrong when drawing up the relations. Please HELP me!
Don't worry, I've found a solution. I confused the #relation and used many-to-many instead of one-to-many. Correct code
data class GameWithHouseworkResult(
#Embedded val game:GameClass,
#Relation(
entity = GameHouseworkResult::class,
parentColumn = "gameId",
entityColumn = "gameId"
)
val houseworkAndResult: List<HouseworkAndResult>
)
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.
Following the official documentation for using #Relation and Junction tables for cross relationship between two tables and I am not having good results.
I am using these two entities:
#Entity(tableName = "Dog",
foreignKeys = arrayOf(
ForeignKey(entity = Owner::class,
parentColumns = arrayOf("ownerId"),
childColumns = arrayOf("dogOwnerId"))
))
data class Dog(
#PrimaryKey val dogId: Long,
val dogOwnerId: Long,
val name: String,
val cuteness: Int,
val barkVolume: Int,
val breed: String
)
#Entity
data class Owner(#PrimaryKey val ownerId: Long, val name: String)
using this table to join them
#Entity(primaryKeys = ["dogId", "ownerId"])
data class DogOwnerCrossRef(
val dogId: Long,
val ownerId: Long
)
and this class for the Query response:
data class DogWithOwners(
#Embedded val dog: Dog,
#Relation(
parentColumn = "dogId",
entity = Owner::class,
entityColumn = "ownerId",
associateBy = Junction(DogOwnerCrossRef::class)
)
val owners: List<Owner>
)
My DAO:
#Transaction
#Query("SELECT * FROM Dog")
fun getdogWithOwners(): List<DogWithOwners>
the result is always null for Owner objects which looks like the #Transaction haven't been made,I am puzzled on how to handle this query without making any join queries.
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
)