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.
Related
I am building an Android application in which I would like to fetch the list of active devices under the project manager.
Trying to put it in different way for better understanding
Project Manager table has list of employees
Employee table has list of devices
Now, we need the list of Project Managers with list of employees with device status either with 1 or 0 based on UI selection.
Entities
#Entity(tableName = TABLE_PROJECT_MANAGER)
data class ProjectManager(
#PrimaryKey
val id: String,
val firstName: String?,
val middleName: String?,
val lastName: String?,
#TypeConverters(EmployeesConverter::class)
var employees: List<Employee>
)
#Parcelize
data class Employee(
val id: String,
val name: String?,
#TypeConverters(DeviceListTypeConverter::class)
val devices : List<Device>? = null
)
#Parcelize
data class Device(
#ColumnInfo(name = "device_id")
#SerializedName("id")
val id: String,
val manufacturer: String?,
val model: String?,
val status: Int,
) : Parcelable
Type Converters:
EmployeesConverter
class EmployeesConverter {
private val moshi = Moshi.Builder().build()
private val membersType = Types.newParameterizedType(List::class.java, Employee::class.java)
private val membersAdapter = moshi.adapter<List<Employee>>(membersType)
#TypeConverter
fun stringToMembers(member: String?): List<Employee>? {
return member?.let {
membersAdapter.fromJson(member)
}
}
#TypeConverter
fun membersToString(members: List<Employee>?): String? {
return members?.let {
membersAdapter.toJson(members)
}
}
}
DeviceListTypeConverter
class DeviceListTypeConverter {
private val moshi = Moshi.Builder().build()
private val membersType = Types.newParameterizedType(List::class.java, Device::class.java)
private val membersAdapter = moshi.adapter<List<Device>>(membersType)
#TypeConverter
fun stringToMembers(member: String?): List<Device>? {
return member?.let {
membersAdapter.fromJson(member)
}
}
#TypeConverter
fun membersToString(members: List<Device>?): String? {
return members?.let {
membersAdapter.toJson(members)
}
}
}
I am little confused on how to achieve this. Please help me out on this.
With what you currently have, you will need a query that has a WHERE clause that will find the appropriate status within the employees column. This is dependant upon how the Type Converter converts the List and the List.
This could be along the lines of:-
#Query("SELECT * FROM $TABLE_PROJECT_MANAGER WHERE instr(employees,'status='||:status)")
fun findProjectManagerWithDevicesAccordingToDeviceStatus(status: String): List<ProjectManager>
NOTE the above will very likely not work as is, you will very likely have to change 'status='||:status according to how the TypeConverter converts the employee list and the device list into the single employees column.
You would call the function with "0" or "1" respectively.
Of course you could use Int for status (Room will convert it to an SQLite string anyway)
In short you are embedding a List with an embedded List into a single value and thus finding the anecdotal needle in that haystack is complicated.
If this were approached from a database perspective then you would have tables (#Entity annotated classes) for each of the List's and as the relationships are probably many-many then tables that map/reference/associate/relate.
So rather than just the ProjectManager table, you would have an Employee table and a Device table and then a table for the mapping of a ProjectManager to the Employee(s) and a table for mapping the Employee to the Device(s). In which case you would have columns with specific values that can be queried relatively efficiently rather than an inefficient search through a complex single relatively large value bloated by the inclusion of data needed solely for the conversion to/from the underlying objects.
Could you please help me? I try to get data from TMDB via Retrofit2 to Room database and display it in Recyclerview. I make 3 api calls to get data about popular movie id/poster/overview/title/genres, with the second call I get movie duration by movie id, and with the 3rd call I get movie cast by movie id.
#GET("movie/popular")
suspend fun getPopularMovies(#Query("api_key") apiKey: String?): PopularResponseModel
#GET("/movie/{movie_id}")
suspend fun getMovieDuration(#Query("api_key") apiKey: String?): MovieDuration
#GET("/movie/{movie_id}/credits")
suspend fun getCrewAndCast(#Query("api_key") apiKey: String?): Cast
And I get 3 models:
#Entity(tableName = "movie")
data class MovieResponse(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "movie_image")
val poster_path: String,
#ColumnInfo(name = "movie_overview")
val overview: String,
#ColumnInfo(name = "movie_title")
val title: String,
#ColumnInfo(name = "movie_genres")
val genre_ids: ArrayList<Int>)
#Entity(tableName = "movie_duration")
data class MovieDuration(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "duration")
val runtime: Int
)
#Entity(tableName = "cast")
data class Cast(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id : Int,
val cast :ArrayList<Cast>
)
The first question is how do I make a #Query request (in the #Dao interface) that will help me to display the popular movie name, title, genre + movie duration associated with that movie in a Recyclerview. I guess they should be mapped somehow by movie id...
For example (John Wick - Action/crime, 2h 10m).
The second question is how to make a #Query request that will help me to associate the exact movie I click on with the movie cast?
(movie John Wick - John Wick: Keanu Reeves...)
The first question is how do I make a #Query request (in the #Dao interface) that will help me to display the popular movie name, title, genre + movie duration associated with that movie in a Recyclerview. I guess they should be mapped somehow by movie id...
As there is a one to one relationship between movie and it's duration then you could simplify matters by including the duration field in the movie. For example instead of MovieResponse and MovieDuration have just MovieResponse as:-
#Entity(tableName = "movie")
data class MovieResponse(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "movie_image")
val poster_path: String,
#ColumnInfo(name = "movie_overview")
val overview: String,
#ColumnInfo(name = "movie_title")
val title: String,
#ColumnInfo(name = "movie_genres")
val genre_ids: ArrayList<Int>,
#ColumnInfo(name = "duration")
val runtime: Int
)
Otherwise you would typically have a POJO class that reflects the two clasess/entities/tables that has two fields; one a MovieResponse and the other a MovieDuration.
e.g.
Option 1 (via #Embedded annotation):-
data class MovieWithEmbeddedDuration(
#Embedded
val movieResponse: MovieResponse,
#Embedded(prefix = "duration_") /* prefix required to disambiguate movie_id column that is in both */
val movieDuration: MovieDuration
)
This would require the use of a JOIN such as #Query("SELECT movie.*, movie_duration.movie_id AS duration_movie_id, movie_duration.duration AS duration_duration FROM movie JOIN movie_duration ON movie.movie_id = movie_duration.movie_id") fun getMoviesWithEmbeddedDuration(): List<MovieWithEmbeddedDuration>
note the complexity due to the need to disambiguate the movie_id column which appears in both tables. AS is used to alter the name of the column when it is output (much easier to have unique column names, as the movie_id is a reference/map/relation to the parent perhaps name the column movie_id_map which better describes the column's usage).
Option 2 (via #Relation annotation)
data class MovieWithRelatedDuration(
#Embedded
val movieResponse: MovieResponse,
#Relation(
entity = MovieDuration::class,
parentColumn = "movie_id",
entityColumn = "movie_id"
)
val movieDuration: MovieDuration
)
this doesn't require the join you simple use a query to get the parent(s) BUT get the POJO with #relation e.g. #Transaction #Query("SELECT * FROM movie") fun getMoviesWithRelatedDuration(): List<MovieWithRelatedDuration>
Here #Relation does the work BUT it works by running separate queries to get the children (mimicking the JOIN), hence why the #Transaction annotation (not needed but warned if omitted). Thus this is less efficient.
Option 2 (via field for the duration)
Instead of embedding the MovieDuration object, you could extract just the duration and have a field for that e.g.
data class MovieWithDurationAsField(
#Embedded
val movieResponse: MovieResponse,
val duration: Int
)
This could be used using #Query("SELECT movie.*,duration FROM movie JOIN movie_duration ON movie_duration.movie_id = movie.movie_id") fun getMoviesWithDurationAsField(): List<MovieWithDurationAsField>
A JOIN is used so this is efficient not that the value for the field is determined according to the column that matches it's name. So if runtime were used then you'd have to rename the output column from duration to runtime.
The second question is how to make a #Query request that will help me to associate the exact movie I click on with the movie cast?
This has basically been explained above (see Option 2). Create the POJO with the parent #Embedded and the children with #Relation annotation and the val being a List/Array of the child objects rather than a single object.
e.g. something like:-
data class MovieWithCastList(
#Embedded
val movieResponse: MovieResponse,
#Relation(
entity = Cast::class,
parentColumn = "movie_id",
entityColumn = "movie_id"
)
val castList: List<Cast>
)
The Query/function would be along the lines of #Query("SELECT * FROM movie") fun getMovieWithCastList(): List<MovieWithCastList>
Note obviously with Retrofit you adapt the queries accordingly.
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.
In the room database I want to auto generate id, I did the bellow code but I get UNIQUE constraint failed error.
How can i get the table to autogenerate id and I do not want to pass id
#Entity
data class OfflineDatax (
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
#ColumnInfo(name = "requestJSON") val requestJSON: String?,
#ColumnInfo(name = "requestCode") val requestCode: String?
)
#Dao
interface OfflineDataDao {
#Query("SELECT * FROM offlinedata")
fun getOfflineData(): Flow<List<OfflineData>>
#Insert()
suspend fun insertOfflineData(offlineData: OfflineData)
}
This is how inserting data
libOfflineDataDao.insertOfflineData(OfflineData(1,"test", "test"))
Thanks
R
we need the unique ID only to access the data but since you are just inserting the data and also you have set the default value as 0 to be inserted, Kotlin is smart enough to insert the data with just the rest two of the parameters. Please let me know if this has helped in any way.
libOfflineDataDao.insertOfflineData(OfflineData("test", "test"))
This should work too.
I have the following entity:
#Entity
class Foo(
#PrimaryKey
#ColumnInfo(name = "id")
val id: Long,
#ColumnInfo(name = "thing1")
val thing1: String,
#ColumnInfo(name = "thing2")
val thing2: String,
#ColumnInfo(name = "thing3")
val thing3: String,
#ColumnInfo(name = "thing4")
val thing4: String
) {
#ColumnInfo(name = "local")
var local: String? = null
}
Where local is information that is not stored on the server, only local to the phone.
Currently when I pull information from the server GSON auto fills in my values, but since "local" does not come from the server it is not populate in that object.
Is there a way that when I call update I can have Room skip the update for the "local" column without writing a custom update to insert into all other columns except "local"? The pain point is that I could have many columns and each new column I add, I would have to add that to the custom insert statement.
I have also thought of a one-to-one mapping from the server entity to a new "local" entity, however now I have to deal with the pain of a join statement everywhere I get my entity since I need the local information.
I was hoping that I could do something like this:
#Entity
class Foo(
#PrimaryKey
#ColumnInfo(name = "id")
val id: Long,
#ColumnInfo(name = "thing1")
val instructions: String,
#ColumnInfo(name = "thing2")
val instructions: String,
#ColumnInfo(name = "thing3")
val instructions: String,
#ColumnInfo(name = "thing4")
val instructions: String
) {
#Ignore
var local: String? = null
}
Using the #Ignore annotation, to try and ignore the local string on a generic update. Then provide a custom update statement to just save the local info
#Query("UPDATE foo SET local = :newLocal WHERE foo.id = :id")
fun updateLocal(id: Long, newLocal: String)
However ROOM seems to be smart enough to check that I used #Ignore on the local property and it will not compile with that update statement.
Any ideas?
Partial Updates got added to Room in 2.2.0
In Dao you do the following:
// Here you specify the target entity
#Update(entity = Foo::class)
fun update(partialFoo: PartialFoo)
And along your entity Foo create a PartialFoo containing the primary key and the fields you want to update.
#Entity
class PartialFoo {
#ColumnInfo(name = "id")
val id: Long,
#ColumnInfo(name = "thing1")
val instructions: String,
}
https://stackoverflow.com/a/59834309/1724097
Simple answer is NO. Room doesn't have conditional insertion or partial insertion.
You have to come up with your insertion logic. The best one I guess is call both database and server for data and just update your server response' local value with your database response' local value.
If you are comfortable with Rx, then you can do something like this
localDb.getFoo("id")
.zipWith(
remoteServer.getFoo("id"),
BiFunction<Foo, Foo, Foo> { localFoo, remoteFoo ->
remoteFoo.local = localFoo.local
remoteFoo
}
)
Another possible way is to write custom #Query that you insert all the values except local, but it's not feasible if you have lots of fields.