Room database query for several Entities - android

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.

Related

Deconflict column names in "join" entity

I have 2 entities:
#Entity(tableName = "author")
data class Author(
#PrimaryKey
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "name")
val name: String
)
data class Book(
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "title")
val title: String,
#ColumnInfo(name = "author_id")
var authorId: String
)
And I would like to join them in a query:
#Query("SELECT * FROM book JOIN author ON author.id = book.author_id AND author.id = :authorId WHERE book.id = :bookId")
fun item(authorId: String, bookId: String): LiveData<BookWithAuthor>
Into this entity:
#Entity
data class BookWithAuthor(
#Relation(parentColumn = "author_id", entityColumn = "id")
val author: Author,
#Embedded
val book: Book
)
However when I do that I get back a BookWithAuthor object in which the author.id and book.id are the same id, in this case they are both the author's id. How do I deconflict the "id" property in the entities in the "join" object?
You can use the #Embedded's prefix to disambiguate the names.
e.g. use :-
#Embedded(prefix="book_")
val book: Book
along with :-
#Query("SELECT author.*, book.id AS book_id, book.title AS book_title, book.author_id AS book_author_id FROM book JOIN author ON author.id = book.author_id AND author.id = :authorId WHERE book.id = :bookId")
Note the above is in-principle code, it has not been tested or run.
You would then change BookWithAuthor to use the prefixed column so :-
#Entity /* not an Entity i.e. Entity = table this just needs to be a POJO */
data class BookWithAuthor(
#Embedded(prefix = "book_")
val book: Book,
/* with (makes more sense to have parent followed by child) */
#Relation(/*entity = Author::class,*/ parentColumn = "book_author_id", entityColumn = "id")
val author: Author
)
However, your comment it assumes that all book ids are unique. In my case I could potentially have duplicate book ids for different authors.
appears to not fit in with the table/entities (schema) you have coded. i.e.
Author entity is fine.
Book though does not have #Entity annotation, nor does it have the obligatory #PrimaryKey annotation if it is an Entity defined in the entities=[....] lis. The assumption made is that the id is/would be the primary key and annotated accordingly and thus unique.
BookWithAuthor You will see that BookWithAuthor has been commented with Not an Entity (table). You cannot have the #Relationship annotation in an Entity that is defined as an Entity to the database (i.e. one of the classes in the entities=[....] list of the #Database annotation).
So unless the primary key of the Book entity/table is not the id or that the authorid is a list of authors then a Book can have only one author. As such it would appear that you only need #Query("SELECT * FROM book WHERE id=:bookId"): LiveData<BookWithAuthor>
if not then coding #Relation will basically ignore your JOIN and select ALL authors but then only pick the first which would be an arbitrary author to complete the author. That is #Relation works by obtaining the parent(s) and then build it's own underlying query to access ALL children of the parent. So whatever Query you supply it ONLY uses this to ascertain the parents.
I suspect that what you want is that a book can have a number of authors and that an author can be an author of a number of books. In this scenario you would typically use a mapping table (can be called other names such as link, reference, associative .... ). If this is the case and you can't ascertain how to create the mapping table via room, then you could ask another question in that regard.
I think the problem here is defining the relation.
My understanding is this is a one to many relationship: One Author (parent) has zero or more Books (entities).
What your #Relation defines is a 1:1 relationship.
If what you want eventually is a BookWithAuthor, why not embedd the Author in Book directly? You would then have the following tables:
#Entity(tableName = "author")
data class Author(
#PrimaryKey
#ColumnInfo(name = "author_id")
val id: String,
#ColumnInfo(name = "name")
val name: String
)
#Entity(tableName = "BookWithAuthor")
data class Book(
#PrimaryKey
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "book_id")
val id: String,
#ColumnInfo(name = "title")
val title: String,
#Embedded
val author: Author
)
And your query can look like this:
#Query("SELECT * FROM BookWithAuthor WHERE book_id = :bookId AND author_id = :authorId")
fun item(authorId: String, bookId: String): LiveData<BookWithAuthor>
After embedding, Book takes the same columns of Author with their exact names. So we need to at least rename either of the id columns to resolve ambuiguity.
Since book ids can be duplicate, we need to introduce a new column as the PrimaryKey for Book.

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).

update multiple tables in Android Room Database

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.

Room Livedata returns incorrect values

I have an audio recorder app, where I enable the user to mark certain points in his recordings with predefined markers. For that purpose I have a MarkerEntity, which is the type of Marker, and a MarkTimestamp, the point at which the user marks a given recording. These entities are connected via a Relation, called MarkAndTimestamp.
#Entity(tableName = "markerTable")
data class MarkerEntity(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "markerName") val markerName: String
)
#Entity(tableName = "markerTimeTable")
data class MarkTimestamp(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "mid") val mid: Int,
#ColumnInfo(name = "recordingId") val recordingId: Int,
#ColumnInfo(name = "markerId") val markerId: Int,
#ColumnInfo(name = "markTime") val markTime: String
)
data class MarkAndTimestamp(
#Embedded val marker: MarkerEntity,
#Relation(
parentColumn = "uid",
entityColumn = "markerId"
)
val markTimestamp: MarkTimestamp
)
The insertion of this data works flawlessly, I checked this via DB Browser for SQLite and Android Debug Database. The problem arises, when I want to display all marks for a recording. I fetch the entries with the following SQL statement.
#Transaction
#Query("SELECT * FROM markerTimeTable INNER JOIN markerTable ON markerTimeTable.markerId=markerTable.uid WHERE markerTimeTable.recordingId = :key")
fun getMarksById(key: Int): LiveData<List<MarkAndTimestamp>>
What ends up happening is, that if the user uses a Marker more than once, all marks created with that Marker have the same MarkerTimestamp row attached to them, specificially, the last row to be inserted with that Marker. The weird thing is, this only happens in the app using Livedata. Using the same query in DB Browser for SQLite returns the correct and desired data.
This is the stored data (correct)
MarkTimestamps
MarkerEntities
And this is the Livedata returned at this point (incorrect)
[
MarkAndTimestamp(marker=MarkerEntity(uid=1, markerName=Mark), markTimestamp=MarkTimestamp(mid=6, recordingId=2, markerId=1, markTime=00:05)),
MarkAndTimestamp(marker=MarkerEntity(uid=2, markerName=zwei), markTimestamp=MarkTimestamp(mid=5, recordingId=2, markerId=2, markTime=00:03)),
MarkAndTimestamp(marker=MarkerEntity(uid=1, markerName=Mark), markTimestamp=MarkTimestamp(mid=6, recordingId=2, markerId=1, markTime=00:05))
]
I also get the following build warning
warning: The query returns some columns [mid, recordingId, markerId, markTime] which are not used by de.ur.mi.audidroid.models.MarkAndTimestamp. You can use #ColumnInfo annotation on the fields to specify the mapping. You can suppress this warning by annotating the method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: mid, recordingId, markerId, markTime, uid, markerName. Fields in de.ur.mi.audidroid.models.MarkAndTimestamp: uid, markerName. - getMarksById(int) in de.ur.mi.audidroid.models.MarkerDao
Why does Room return the wrong data and how do I fix this?
So, I still don't know what caused the behaviour described in my original post. My guess is, that the realtion data class and the SQL query interfered in some way, producing the cinfusing and incorrect outcome.
I solved my problem nonetheless. I needed to change
data class MarkAndTimestamp(
#Embedded val marker: MarkerEntity,
#Relation(
parentColumn = "uid",
entityColumn = "markerId"
)
val markTimestamp: MarkTimestamp
)
to
data class MarkAndTimestamp(
#Embedded val marker: MarkerEntity,
#Embedded val markTimestamp: MarkTimestamp
)
This makes sure, that all fields returned by the query are included in the data class.

"no such column" SQL error from Android Room and multiple #Embedded fields on POJO

The original question is at the very bottom. I have created a minimal (non-)working example of my problem which is hopefully easier to read through. The example is here at gitlab. There is a readme describing the problem. I am pasting some parts of the project here.
The data model is plain simple:
Owner <--(1:N)-- Child --(N:1)--> ReferencedByChild
All I want to do is to read an Owner from the database with all its associated Child objects and for each Child object
also the ReferencedByChild object that it references.
The whole code that reproduces my problem is below. What I am not 100% sure about is the #Relation on the OwnerWithEverything POJO. See below please.
#Database(
entities = [
Owner::class,
Child::class,
ReferencedByChild::class
],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun appDao(): AppDao
}
#Dao
abstract class AppDao {
#Insert
abstract fun insertOwner(owner: Owner): Long
#Insert
abstract fun insertChild(child: Child): Long
#Insert
abstract fun insertReferencedByChild(referencedByChild: ReferencedByChild): Long
#Query("SELECT * FROM Child INNER JOIN ReferencedByChild ON Child.referencedByChildId = ReferencedByChild.refByChildId ORDER BY Child.childText")
abstract fun findAllChildrenWithReferencedClasses(): List<ChildWithReferenced>
// Commenting this query out makes the build pass, so something here is incorrect.
#Query("SELECT * FROM Owner")
abstract fun findOwnersWithEverything(): List<OwnerWithEverything>
}
// ENTITIES
#Entity
data class Owner(
#PrimaryKey(autoGenerate = true)
val ownerId: Long,
val ownerText: String
)
#Entity(
foreignKeys = [
ForeignKey(
entity = Owner::class,
parentColumns = arrayOf("ownerId"),
childColumns = arrayOf("referencedOwnerId"),
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = ReferencedByChild::class,
parentColumns = arrayOf("refByChildId"),
childColumns = arrayOf("referencedByChildId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class Child(
#PrimaryKey(autoGenerate = true)
val childId: Long,
val childText: String,
val referencedOwnerId: Long,
val referencedByChildId: Long
)
#Entity
data class ReferencedByChild(
#PrimaryKey(autoGenerate = true)
val refByChildId: Long,
val refText: String
)
// POJOS
// The Child has exactly one ReferencedByChild reference. This POJO joins those two
class ChildWithReferenced(
#Embedded
var child: Child,
#Embedded
var referencedByChild: ReferencedByChild
)
class OwnerWithEverything {
#Embedded
var owner: Owner? = null
#Relation(
parentColumn = "ownerId",
entityColumn = "referencedOwnerId",
entity = Child::class // which entity should be defined here?
)
var childrenWithReferenced: List<ChildWithReferenced>? = null
}
Building this code results in this error message:
error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: refByChildId)
I think that the Owner query is badly constructed, but I am not entirely sure. If that is the problem, what is the correct way to construct the query?
This is the original question
I have a nested POJO structure that should represent a single Game having multiple Rounds and each Round has a single Topic associated with it:
class GameWithRounds {
#Embedded
var game: Game? = null
#Relation(
parentColumn = "id",
entityColumn = "gameId",
entity = RoundRoom::class
)
var rounds: List<RoundWithTopic>? = null
}
class RoundWithTopic(
#Embedded
var round: RoundRoom,
#Embedded(prefix = "topic_")
var topic: Topic
)
The embedded annotation on Topic specifies a prefix because there are clashing id properties.
The Room Query that can fetch those classes is:
#Query("SELECT Topic.id as topic_id, Topic.name as topic_name, (...), RoundRoom.* FROM RoundRoom INNER JOIN Topic ON RoundRoom.topicId = Topic.id")
abstract fun findRoundsWithTopics(): List<RoundWithTopic>
However, building the project gives me Room errors:
There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: topic_id)
Even though when I induce a warning about which fields are actually present, this is what Room tells me:
Columns returned by the query: topic_id, topic_name, topic_description, topic_language, topic_keywords, topic_sourceUrls, topic_furtherUrls, topic_questions, order, gameId, topicId, status, id. Fields in cz.melkamar.sklapecka.model.RoundWithTopic: order, gameId, topicId, status, id, topic_id, topic_name, topic_description, topic_language, topic_keywords, topic_sourceUrls, topic_furtherUrls, topic_questions, topic_image.
The topic_id column is there in the query result! Why am I getting this error?
For completeness, this is the entities:
#Entity
data class Game(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
#Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
)
data class GameConfigurationEmbed(
var secondsPerTurn: Int,
var maxSecondsPerTurn: Int,
var bonusSecondsPerAnswer: Int
)
#Entity(
foreignKeys = [
ForeignKey(
entity = Game::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("gameId"),
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Topic::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("topicId"),
onDelete = ForeignKey.CASCADE
)
]
)
#TypeConverters(RoomConverters::class)
data class RoundRoom(
val order: Int,
var gameId: Long,
val topicId: String,
var status: RoundStatus = RoundStatus.CREATED,
#PrimaryKey(autoGenerate = true)
val id: Long = 0
) {
enum class RoundStatus {
CREATED, UPCOMING, IN_PROGRESS, FINISHED
}
}
#Entity
data class Topic(
#PrimaryKey val id: String,
val name: String,
val description: String,
val language: String,
val keywords: List<String>,
val sourceUrls: List<String>,
val furtherUrls: List<String>,
val questions: List<String>,
val image: ByteArray?
)
After some research, specifically looking at this link : How can I represent a many to many relation with Android Room?
the only answers we found would be to
either create a hand-made sql query to handle this type of situation
where you have a many-to-many relationship
OR
to alternatively have an additional joining entity which gets
updated as the rest of the objects are updated. With this approach, you can get the ID's and then create additional queries as needed
It seems embedded fields and type converter is not properly used on observing the question. I don't want to go in detail in the solution of the question since it is trying to use complex relations and I cannot test it replicating in my machine.
But I want to provide insight on using Embedded fields and TypeConverter.
Let's take an example from the question above:
Game table has fields
id, secondsPerTurn, maxSecondsPerTurn, bonusSecondsPerAnswer.
It is okay to create entity like below.
#Entity
data class Game(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
#Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
)
data class GameConfigurationEmbed(
var secondsPerTurn: Int,
var maxSecondsPerTurn: Int,
var bonusSecondsPerAnswer: Int
)
Here in the SQLite table, data is actually stored in four different columns but Room does CRUD operation based on data class structure giving higher feasibilty to the developers.
TypeConverter
Type converter will be helpful if we want to store non primitive data types or same type of data which will not be covered by #Embedded.
For example, a football game can be held in two places: home and away. Home and Away can have the same field names like placeName, latitude, longitude. In this case, we can create data class and type converters like below:
data class GamePlace(
val placeName:String,
val latitude:String,
val longitude:String
)
#Entity
data class Game(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
#Embedded
val gameConfigurationEmbed: GameConfigurationEmbed
#TypeConverters
var home: GamePlace? = null,
#TypeConverters
var away: GamePlace? = null,
)
object Converters {
private val gson = Gson()
#TypeConverter
#JvmStatic
fun fromGamePlace(gamePlace: GamePlace?): String? {
return if (gamePlace == null) null else gson.toJson(gamePlace)
}
#TypeConverter
#JvmStatic
fun toGamePlace(jsonData: String?): GamePlace? {
return if (jsonData.isNullOrEmpty()) null
else gson.fromJson(jsonData, object : TypeToken<GamePlace?>() {}.type)
}
}
While using type converter, converter class should be defined in database class as below:
#Database(
entities = [Game::class /* ,more classes here*/],
version = 1
)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun gameDao(): GameDao
//.....
}
I hope this will help to deal with Embedded and TypeConverter in Room.

Categories

Resources