is it possible to create name of table in #Database annotitation? - android

i want to create two table with one model.how can i do that?
this is my model
#Entity(tableName = "user_table")
class UserModel
: Serializable {
#PrimaryKey
#NonNull
var _id: String = ""
var userName: String? = null
var userAvatar: String? = null
#TypeConverters(ConvertersDAO::class)
var birthDate: Date? = null
var gender: String? = null
}
and my database
#Database(entities = [UserModel::class], version = 1,exportSchema = false)
abstract class UserRoomDatabase : RoomDatabase() {
abstract fun getUserDao() : UserDAO
companion object{
#Volatile
private var instance : UserRoomDatabase?= null
fun getInstance(context : Context): UserRoomDatabase{
if(instance==null)
instance = Room
.databaseBuilder(context,UserRoomDatabase::class.java,"UserRoomDatabase")
.build()
return instance!!
}
}
}

i want to create two table with one model.how can i do that?
In short you cannot as it is the #Entity annotation and the inclusion of the class as an Entity in the #Database class which defines the Class as a table.
The closest that you would get to using a Class as a model would be to either a) inherit the UserModel class or b) embed the UserModel class into another class using #Embedded.
However, you would still need either separate Dao's, as the table name is specified within and cannot be a variable, or use raw queries that are not checked at compile time, where the table name could be passed as a variable.
It would probably be far simpler, to have a single table with a column, possibly indexed, that indicates the group (User) to which a row belongs.

Related

Error when using TypeConverter with Room: Cannot figure out how to save this field into database

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.

How to insert a List to a table with Dao Room in Android when there is a primarykey

I have a data class contains id and code.
And I have a List contains codes only.
how to insert the codes into table without making up ids?
Actually I do not need the id column at all, but it seems Room requires a primary key, and codes cannot be primary key.
Room:
#Entity(tableName = "raw_table")
data class Raw(
#PrimaryKey(autoGenerate = true)
var id: Long = 0L,
#ColumnInfo(name = "code")
var code: String = "",
...
List and loop:
val codeList : List<String> = ...
for (code in codeList){
// wrong here, I need the id, but I do not have ids.
RawDao.insert(code)
}
Create a Dao like (or amend an existing Dao to include the #Insert as below)
:-
#Dao
interface RawDao {
#Insert
fun insertManyRaws(raws: List<Raw>): List<Long>
}
Create the #Database as normal including abstract function to get the RawDao. e.g.
:-
#Database(entities = [Raw::class],version = 1)
abstract class RawDatabase: RoomDatabase() {
abstract fun getRawDao(): RawDao
}
You can then use something like :-
class MainActivity : AppCompatActivity() {
lateinit var db: RawDatabase
lateinit var dao: RawDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rawList: List<Raw> = arrayListOf(Raw(0L,"Test1"),Raw(0L,"Test2"),Raw(0L,"etc...."))
db = Room.databaseBuilder(this,RawDatabase::class.java,"raw.db")
.allowMainThreadQueries()
.build()
dao = db.getRawDao();
dao.insertManyRaws(rawList) //<<<<<<<<<< ADD All Raws at once
}
}
Running the above results in :-
i.e. The 3 Raw's have been added to the database, as seen by using AS's Database Inspector
Note the dao.insertManyRaws call returns, as a List, the inserted id's (if any are -1 then that Raw was not inserted)

Room doesn't detect #Typeconverters

This is my only Entity:
#Entity(tableName = "sticker_packs")
class StickerPack(
#PrimaryKey(autoGenerate = true)
val identifier: Int = 0,
var name: String,
var publisher: String,
#TypeConverters(StickerConverter::class)
private var stickers: List<Sticker> = emptyList())
This is my custom Sticker class:
data class Sticker(
val emojis: List<String>,
var imageDir: String,
var size: Long = 0L)
This is my Database class:
#Database(entities = [StickerPack::class], version = 1)
//I get this same error with or without my Typeconverters here
#TypeConverters(StickerConverter::class)
abstract class StickerPacksDatabase: RoomDatabase() {
abstract val stickerPacksDao: StickerPacksDao
companion object{
#Volatile
private var INSTANCE: StickerPacksDatabase? = null
fun getInstance(context: Context): StickerPacksDatabase{
synchronized(this){
var instance = INSTANCE
if(instance == null){
instance =
Room.databaseBuilder(context.applicationContext, StickerPacksDatabase::class.java, "sticker_packs_database")
.build()
INSTANCE = instance
}
return instance
}
}
}
}
And this is my typeconverter
class StickerConverter {
#TypeConverter
fun stickersToJson(stickers: List<Sticker>): String = Gson().toJson(stickers)
#TypeConverter
fun jsonToStickers(json: String): List<Sticker> = Gson().fromJson(json)
private inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, object: TypeToken<T>() {}.type)}
I'm not trying to do anything fancy just converting a list into a string with Gson, yet when trying to build the project I get the following error:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
But that's exaclty what I'm trying to do. I may have made a mistake somewhere but I can't seem to find it. I tried moving the #TypeConverters notation everywhere posible and the problem persists
You need to add the #TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase:
Database(entities = arrayOf(Sticker::class), version = 1)
#TypeConverters(StickerConverter::class)
abstract class AppDatabase : RoomDatabase{
}

ROOM Single Database Multiple Entities, Insert Query for different Tables

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.

This not applicable to target 'member propertty without backing field or delegate' and passing value error with Room on Android?

I am using Room. I need to use SELETC, INSERT, DELETE all.
This is what I implemented:
#Dao
interface UserDao {
#Query("SELECT * FROM user WHERE m_id IN (:m_id)")
fun loadAllByIds(userSeqs: IntArray?): List<User?>?
#Query("SELECT * FROM user")
val all: List<User?>?
}
#Entity(tableName = "user")
data class User (
#PrimaryKey(autoGenerate = true) val seq: Long,
// this name is used in dao as column name
#ColumnInfo(name = "m_id") val mId: String?,
#ColumnInfo(name = "k_id") var kId: String?,
#ColumnInfo(name = "created_at") var createdAt: String?
)
#Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
public static final String DB_NAME = "MyDatabase.db";
private static MyDatabase INSTANCE;
public abstract UserDao userDao();
public static MyDatabase getMyDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context, MyDatabase.class, DB_NAME).build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
#Query("SELECT * FROM deliver") says
This not applicable to target 'member property without backing field or delegate'
#get:Query("SELECT * FROM deliver") make it disappear. But, I don't know why it does. Does it solve the problem? What does it do?
The #Query annotation is to be placed on a method. A kotlin property is kind of "a field + getter method + setter method", but it's not a field or a method per se. When you specify the annotation target via #get:Query, you are basically telling the compiler to put this annotation on the property's getter method, which makes the whole thing work.
I had the same issue, I figure out a solution by checking my import.
I am using retrofit and Room, by mistake I imported retrofit #Query version instead of a room. Please check your import as well.

Categories

Resources