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.
Related
How is possible to add multiple databases in room using ViewModel class? Do I need a different ViewModel class and a different #Entity and a different #Dao for each database, or can I use the same #Entity and #Dao of another database, sorry if the question is pretty obvious, thanks a lot in advance
I tried different Databases with the same #Entity, #Dao and ViewModel, Didn't work
I also tried Different everything, the database appeared but i couldn't query it, please help :(
Yes, you will need a separate Entity class, and Dao class for each database in Room. This is because each database will have its own set of data, and the Entity and Dao classes are used to define the schema and provide access to the data in the database.
// Define the Entity class for each database
#Entity(tableName = "data1")
data class Data1(
#PrimaryKey(autoGenerate = true) val id: Int,
val data: String
)
#Entity(tableName = "data2")
data class Data2(
#PrimaryKey(autoGenerate = true) val id: Int,
val data: String
)
// Define the Dao class for each database
#Dao
interface Data1Dao {
#Query("SELECT * FROM data1")
fun getAll(): List<Data1>
#Insert
fun insert(data: Data1)
// Other database operations...
}
#Dao
interface Data2Dao {
#Query("SELECT * FROM data2")
fun getAll(): List<Data2>
#Insert
fun insert(data: Data2)
// Other database operations...
}
// Define the ViewModel class for each database
class MyViewModel1: ViewModel() {
val db1: Database1
val db2: Database2
init {
db1 = Room.databaseBuilder(context, Database1::class.java,
"db1").build()
db2 = Room.databaseBuilder(context, Database2::class.java,
"db2").build()
}
fun getDataFromDb1(): List<Data1> {
return db1.data1Dao().getAll()
}
fun getDataFromDb2(): List<Data2> {
return db2.data2Dao().getAll()
}
// Other database operations...
}
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.
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.
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)
I would like to add value, date and details to the current pb. I am receiving an error 'conflicting declaration' in the database for pbInfo. How should I fix this error?
#Entity(tableName = "pb_table")
data class Pb(#PrimaryKey
val pb: String)
#Entity
data class PbInfo(#PrimaryKey
var value: Double,
var date: Int,
var details: String)
#Dao
interface PbInfoDao {
#Insert
fun update(vararg pbInfo: PbInfo): LongArray
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.pbDao(), database.pbInfo())
}
}
}
suspend fun populateDatabase(pbDao: PbDao, pbInfoDao: PbInfoDao) {
pbDao.deleteAll()
var pb = Pb("Squat")
pbDao.insert(pb)
var pbInfo = PbInfo(122.5, 28, "I was feeling on top form today!")
First of all, you have two Entities in a single class (possibly the conflict)
So, add separate class for separate Entity.
Then, in your RoomDatabase abstract class, add two Entity Classes like this (and also create separate Dao interface classes):
#Database(entities = [(Pb::class), (Pbinfo::class)] ,version = 2)
abstract class YourRoomDatabaseClass: RoomDatabase(){
...
abstract fun pbDao() : PbDao
abstract fun pbinfoDao(): PbinfoDao
...
}
This should solve the conflicting of Entity classes. I have a single database with two Entities just like this and running without any problems. (Please mind me because I don't know Kotlin Syntax)
Use this
#Insert(onConflict = OnConflictStrategy.REPLACE)
instead of
#Insert