#Entity
data class Products(
val id:String,
val title: String,
val description: String,
val imgUrl: String,
val usageRules : List<String>, //what happens here?
)
i am playing around with ROOM for android and most of the examples i have seen does not explain how you would create a Entity table that has a arrayList within it.
There is #Embedded which seems to only nest another object but i am trying to nest a LIST of objects called usageRules
Do i need to create a seperate Usage Rule table? The thing is, each usage rule can be included on multiple products above and also a product can have multiple usage rules defined so it is a many to many relationship.
Is this possible with ROOM?
I know that in Realm i can simply convert the UsageRule List<> to a RealmList<>. is there an equivalent in ROOM?
Another alternative is that because it is just a lis of strings. i could simply create a new table entity field called allUsage that stores all the usageRules on a particular product with a seperator and then later when i want to construct the data object again from ROOM i can grab its contents by doing allUsage.Split(..)
Still would rather do it another way as i may come across a scenarionwhere the usageRules are not just a list of strings but a list of objects....
Try using ArrayList:-
#Entity
data class Products(
val id:String,
val title: String,
val description: String,
val imgUrl: String,
val usageRules : ArrayList<String>, //what happens here?
)
Need to use Convertors for this:-
class Converters {
companion object {
#TypeConverter
#JvmStatic
fun fromString(value: String): ArrayList<String>? {
val listType = object : TypeToken<ArrayList<String>>() {}.type
return Gson().fromJson(value, listType)
}
#TypeConverter
#JvmStatic
fun fromArrayList(list: ArrayList<String>?): String {
val gSon = Gson()
return gSon.toJson(list)
}
}
}
And define Convertors Here:-
#Database(entities = [TableModel::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class RDatabase : RoomDatabase() {
abstract val tableDAO: TableDAO
}
One extra implementation needs:-
implementation 'com.google.code.gson:gson:2.8.5'
My code is perfectly working by this
Related
I get this error:
error: Cannot figure out how to save this field into database. You can
consider adding a type converter for it.
private final java.util.List<com.example.Detail.Stat> stats = null;
I can't figure it out. I have Added the typeconverter to the database, but still get this error. Any ideas what I do wrong?
Entity:
#Entity
data class Detail(
#PrimaryKey val id: Int,
val stats: List<Stat>,
val types: List<String>
){
data class Stat(
val baseStat: Int,
val stat: String
)
}
Typeconverter:
#ProvidedTypeConverter
class StatConverter #Inject constructor(
private val moshi: Moshi
){
#TypeConverter
fun fromJson(value: String): List<Detail.Stat>? {
val listType = Types.newParameterizedType(List::class.java, Detail.Stat::class.java)
val adapter: JsonAdapter<List<Detail.Stat>> = moshi.adapter(listType)
return adapter.fromJson(value)
}
#TypeConverter
fun toJson(type: List<Detail.Stat>?): String {
val listType = Types.newParameterizedType(List::class.java, Detail.Stat::class.java)
val adapter: JsonAdapter<List<Detail.Stat>> = moshi.adapter(listType)
return adapter.toJson(type)
}
}
Database:
#Database(entities = [Detail::class], version = 1, exportSchema = true)
#TypeConverters(StatConverter::class)
abstract class Database : RoomDatabase() {
abstract fun detailDao(): DetailDao
companion object{
const val DATABASE = "database"
}
}
DI module where room is provided:
#Singleton
#Provides
fun provideAppDatabase(
application: Application,
statConverter: StatConverter
): Database {
return Room
.databaseBuilder(application, Database::class.java,
Database.DATABASE
)
.addTypeConverter(statConverter)
.fallbackToDestructiveMigration()
.build()
}
EDIT:
The typeconverter code works fine with the other field (List) in the entity, but not with List.
Apparently, something about your nested data class is causing problems, and so moving Stat out from being nested in Detail helped.
If you have the time, you might try creating a scrap project that illustrates the problem, then file an issue on the issue tracker, attaching that project as a demonstration of the problem. I don't see anything that quite matches, but there are a lot of issues, so perhaps I missed it.
I didn't run your code or test this out, but from eyeballing it here, is it possible it's the difference between nullable List<Detail.Stat>? in the type converter and non-nullable List<Stat> in the entity? Either make entity nullable or type-converter non-nullable and see if it works.
Room issue:
error: Not sure how to convert a Cursor to this method's return type (androidx.lifecycle.LiveData<java.util.List<java.util.List<com.example.shoppingapp.room.GroceryItems>>>).
public abstract androidx.lifecycle.LiveData<java.util.List<java.util.List<com.example.shoppingapp.room.GroceryItems>>> getAllGroceryItems();
Room doesn't operate
LiveData<List<List<itemElement>>>
I have 2 fragments.
One with List<ItemFirstFragment> and second with List<List<GroceryItem>>
Both fragments are lists with recyclerViews, but when I click on first element in List I send id and name to second fragment then I would like to show in the second fragment
list[positionFromFirstFragment] -> It will show the second list like:
listOf(positionFromFirstFragment) as -> List<List<GroceryItem>> will gives me List<GroceryItem>
The same problem with map:
#Query("SELECT * FROM grocery_items")
fun getAllGroceryItems() : LiveData<Map<String, List<GroceryItems>>>
How to solve it?
EDIT:
GroceryItems
package com.example.shoppingapp.room
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
#Entity(tableName = "grocery_items")
data class GroceryItems (
#ColumnInfo(name = "itemName")
var itemName: String,
#ColumnInfo(name = "itemQuantity")
var itemQuantity: Int,
#ColumnInfo(name = "itemPrice")
var itemPrice: Int
) {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
}
EDIT:
Solved
Just use map or one to many relation with proper query. Check documentation
How to handle one to many entity relationship with room and recyclerviews?
You need to add a Room TypeConverter to convert this object type to json. That is what Room can store and retrieve.
Add or update dependencies
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
//Moshi Library Dependencies
implementation "com.squareup.moshi:moshi:1.12.0"
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
Create a Converter class for Room Database
class TypeConverter {
//initialize Moshi
private val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
#TypeConverter
fun genreToString(lists: List<List<GroceryItems>>): String =
moshi.multipleListsToJson(lists)
#TypeConverter
fun stringToGenre(json: String): List<List<GroceryItems>>? =
moshi.jsonToMultipleLists(json)
}
Use these extensions
inline fun <reified T> Moshi.multipleListsToJson(data: List<List<T>>): String =
adapter<List<List<T>>>(
Types.newParameterizedType(
List::class.java,
List::class.java,
T::class.java
)
).toJson(data)
inline fun <reified T> Moshi.jsonToMultipleLists(json: String): List<List<T>>? =
adapter<List<List<T>>>(
Types.newParameterizedType(
List::class.java,
List::class.java,
T::class.java
)
).fromJson(json)
Finally add your TypeConverter to your Database by using the #TypeConverters annotation
#TypeConverters(TypeConverter::class)
I have moshi with retrofit and I want to send dynamic inner data object to backend.
Lets say I need to patch some data on my endpoint:
#PATCH("/animals/{animalId}/attributes")
suspend fun updateAnimal(#Path("animalId") animalId: String, #Body request: MyPetsRequest)
But my AnimalAttributes inner object is dynamic.
#JsonClass(generateAdapter = true)
#Parcelize
data class MyPetsRequest(
#Json(name = "name") val name: String,
#Json(name = "attributes") val attributes: AnimalAttributes
) : Parcelable
interface AnimalAttributes : Parcelable // or sealed class possible when share same base fields
#JsonClass(generateAdapter = true)
#Parcelize
data class DogAttributes(
#Json(name = "bark") val bark: Boolean,
#Json(name = "bite") val bite: Boolean,
) : AnimalAttributes
#JsonClass(generateAdapter = true)
#Parcelize
data class CatAttributes(
#Json(name = "weight") val weight: Int,
) : AnimalAttributes
I tried create a custom adapter for Moshi, but when I use #ToJson inner object attributes data are escaped (so complete object is send as one parameter).
class AnimalAttributesAdapter {
#ToJson
fun toJson(value: AnimalAttributes): String {
val moshi = Moshi.Builder().build()
return when (value) {
is DogAttributes -> moshi.adapter(DogAttributes::class.java).toJson(value)
is CatAttributes -> moshi.adapter(CatAttributes::class.java).toJson(value)
else -> throw UnsupportedOperationException("Unknown type to serialize object to json: $value")
}
}
/**
* Not supported, this object is used only one way to update data on server.
*/
#FromJson
fun fromJson(value: String): AnimalAttributes = throw UnsupportedOperationException()
}
Moshi.Builder().add(AnimalAttributesAdapter())
Result in this case looks like:
{"name":"my pet name","attributes":"{\"bark\":\"true\", \"bite\":\"false\"}"}
When I try to use PolymorphicJsonAdapterFactory it added next parameter between attributes which is not acceptable for my use case.
PolymorphicJsonAdapterFactory.of(AnimalAttributes::class.java, "animal")
.withSubtype(DogAttributes::class.java, "dog")
.withSubtype(CatAttributes::class.java, "cat")
Result in this case added attribute animal: {"name":"my pet name","attributes":{"animal":"dog","bark":true, "bite":false}}
I don't need to deserialize object from json to data class. I only need create one way serialization to json.
I have the following class:
#Entity(tableName = "dutyday_table")
data class DutyDay(
#PrimaryKey
val date: Date,
val showTime: Time,
val closingTime:Time
)
and another class which uses objects from class "DutyDay":
#Entity(tableName = "tour_table")
data class Tour(
#PrimaryKey
val day1: DutyDay?,
val day2: DutyDay?,
val day3: DutyDay?,
val day4: DutyDay?,
val day5: DutyDay?,
val day6: DutyDay?,
val day7: DutyDay?,
val totalHours: Time
)
and the following converter:
class Converters {
#TypeConverter
fun longToDate(value: Long?): Date? {
return value?.let { Date(it) }
}
#TypeConverter
fun dateToLong(date: Date?): Long? {
return date?.time
}
#TypeConverter
fun longToTime(value: Long?): Time? {
return value?.let { Time(it) }
}
#TypeConverter
fun timeToLong(date: Time?): Long? {
return date?.time
}
}
The converter is properly annotated in the database, I have used Room Database and converters before they normaly work fine. But with this example Android Studio is complaining that it cannot figure out how to store the fields from class "Tour", I guess because it uses the DutyDays created in class "DutyDay". Do I somehow have to create another converter for the properties in "Tour"?
Thanks for your help .
There are 3 objects in your entities that are unknown for Room - Date, Time, DutyDay. You've described TypeConverter just for Date and Time, so the problem is that Room doesn't know how to persist DutyDay.
I think you choices are:
Add one more converter for DutyDay (to use Gson-converting to string, for example. Here is link with similar decision).
To use foreign keys inside Tour table instead of DutyDay's references.
I have a small query, I have been confused on how to insert an object into a different entity.
I tried a work around doing:
#Query("INSERT INTO card_table VALUES(:id, :pid, :question, :answer, :type, :completed)")
suspend fun insertCard(id: UUID, pid: UUID, question: String, answer: String, type: CardType, completed: Boolean)
However, this has not worked.
My question is, how do I insert an object into different entity tables with a single database.
Note -> The insert() query works for inserting decks into the deck_table. The issue correlates to the card_table as I am unsure on how to reference it.
Thanks
database
#Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(deck: Deck)
#Insert
suspend fun insertCard(card: FlashCard)
#Database(entities = [Deck::class, FlashCard::class], version = 8, exportSchema = false)
#TypeConverters(DeckTypeConverters::class)
abstract class FlashCardDB : RoomDatabase() {
abstract fun DeckDAO(): DeckDAO
companion object {
private var INSTANCE: FlashCardDB? = null
fun getDatabase(context: Context, scope: CoroutineScope): FlashCardDB {
val tmpInstance = INSTANCE
if (tmpInstance != null) return tmpInstance
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
FlashCardDB::class.java,
"flash_cards_database"
).build() //.fallbackToDestructiveMigration() will causes users to loose data during migrations (use only for testing purposes)
INSTANCE = instance
return instance
}
tables
#Entity(tableName = "deck_table")
#Parcelize
data class Deck(
#PrimaryKey #ColumnInfo(name = "id") val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "title") var title: String,
#ColumnInfo(name = "date") var date: Calendar,
#ColumnInfo(name = "completed") var completed: Boolean
) : Parcelable {
#Entity(tableName = "card_table")
#Parcelize
data class FlashCard(
#PrimaryKey #ColumnInfo(name = "id") val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "parent_id") var pid: UUID,
#ColumnInfo(name = "question") var question: String,
#ColumnInfo(name = "answer") var answer: String,
#ColumnInfo(name = "type") var type: CardType,
#ColumnInfo(name = "completed") var completed: Boolean)
: Parcelable {
}
My question is, how do I insert an object into different entity tables with a single database
To insert object you need to put method with #Insert in interface or abstract class with #Dao
When you build your project - Room will generate implementation of your #Dao-interface. It would be plain class, but written in Java (for DeckDAO interface generated class would be DeckDAO_Impl.java). Also Room will implement #Insert-method (since you tell in this method what object you want to insert and Room knows its corresponding table's name). Let's say (to be simple) that implementation of this method would be executing query insert into _tableName_ values (...).
#Insert
suspend fun insert(deck: Deck) <-- Room will generate implementation "insert into deck_table values (..."
#Insert
suspend fun insert(card: FlashCard) <-- Room will generate implementation "insert into card_table values (..."
To get access to this implemented class' methods you should put your dao-interface in abstract class that extends Database. After build Room will make for you implementation of this abstract class and you can invoke method insert in your example with just:
FlashCardDB.getDatabase().DeckDAO().insert(deck)
or
FlashCardDB.getDatabase().DeckDAO().insert(card)
It's not necessary to create separate dao-interface for each entity. In theory you could use single dao-interface with all insert/delete/update/query-methods. Then all you need is to get access to all these methods through single FlashCardDB.getDatabase().DeckDAO() endpoint. You can put there 10 insert-methods with the same insert name but they should differ with parameter's type (it's just a method overloading) or you could use different method's names for clarity.
Though in practice (and in most tutorials) insert-delete-update-query methods for different entities for convenience are placed in different dao-interfaces. To achieve that you should put all these dao-interfaces to Database class. Let's say:
abstract class FlashCardDB : RoomDatabase() {
abstract fun deckDAO(): DeckDAO
abstract fun cardDAO(): CardDAO
So decision is up to you.
If i'm getting your question correctly you want to insert the card data into the database right? What you need to do is have a DAO for each database model. So your Card database model will have its own DAO:
#Dao
public abstract class CardDao {
#Insert
fun insert(card: Card)
}
#Database(entities = [Deck::class, FlashCard::class], version = 8, exportSchema = false)
#TypeConverters(DeckTypeConverters::class)
abstract class FlashCardDB : RoomDatabase() {
abstract fun deckDao(): DeckDao
abstract fun cardDao(): CardDao
...
Kindly let me know if this answers your question.