I have following interface where I have created the standard crud methods and annotated the methods with insert, update, and delete.
interface BaseDao<T> {
#Insert
fun insert(table: T): Single<Long>
#Insert
fun insert(vararg table: T): Single<List<Long>>
#Update
fun update(table: T): Single<Int>
#Delete
fun delete(table: T): Single<Int>
}
I then create a interface for the DAO
#Dao
interface WeatherDao : BaseDao<WeatherTable> {
override fun insert(table: WeatherTable): Single<Long>
override fun insert(vararg table: WeatherTable): Single<List<Long>>
override fun update(table: WeatherTable): Single<Int>
override fun delete(table: WeatherTable): Single<Int>
#Query("SELECT * FROM weatherTable")
fun getAllWeather(): Single<List<WeatherTable>>
#Query("SELECT * FROM weatherTable WHERE id = :id LIMIT 1")
fun getWeatherById(id: Long): Single<WeatherTable>
#Query("SELECT count(*) FROM weatherTable")
fun count(): Single<Int>
}
When I compile I get a lot of error like this following:
error: An abstract DAO method must be annotated with one and only one of the following annotations: Insert,Delete,Query,Update,RawQuery
public abstract io.reactivex.Single<java.lang.Long> delete(#org.jetbrains.annotations.NotNull()
Because when I inherited from the interface. I have to manually add the #Insert, #Update, and #Delete.
Just wondering why these annontations are added automatically in the my WeatherDao interface.
So I now have to manually add them like this:
#Dao
interface WeatherDao : BaseDao<WeatherTable> {
#Insert
override fun insert(table: WeatherTable): Single<Long>
#Insert
override fun insert(vararg table: WeatherTable): Single<List<Long>>
#Update
override fun update(table: WeatherTable): Single<Int>
#Delete
override fun delete(table: WeatherTable): Single<Int>
#Query("SELECT * FROM weatherTable")
fun getAllWeather(): Single<List<WeatherTable>>
#Query("SELECT * FROM weatherTable WHERE id = :id LIMIT 1")
fun getWeatherById(id: Long): Single<WeatherTable>
#Query("SELECT count(*) FROM weatherTable")
fun count(): Single<Int>
}
Just wondering if I am using this wrong:
Following this google repo, you are not doing the abstractation correctly. To sum up, you do not need to have the inserts/updates/deletes in your #Dao Interface and it should be abstract.
interface BaseDao<T> {
/**
* Insert an object in the database.
*
* #param obj the object to be inserted.
*/
#Insert
fun insert(obj: T)
/**
* Insert an array of objects in the database.
*
* #param obj the objects to be inserted.
*/
#Insert
fun insert(vararg obj: T)
/**
* Update an object from the database.
*
* #param obj the object to be updated
*/
#Update
fun update(obj: T)
/**
* Delete an object from the database
*
* #param obj the object to be deleted
*/
#Delete
fun delete(obj: T)
}
#Entity(tableName = "data")
data class Data(#PrimaryKey val id: String, val value: String)
#Dao
abstract class DataDao : BaseDao<Data>() {
/**
* Get all data from the Data table.
*/
#Query("SELECT * FROM Data")
abstract fun getData(): List<Data>
}
Related
I have
#Dao
interface ContactDao {
#Query("SELECT * FROM ContactItem")
fun getAll(): Flow<List<ContactItem>>
#Insert
fun insertAll(vararg todos: ContactItem)
#Delete
fun delete(todo: ContactItem)
#Update
fun update(note: ContactItem)
#Query("DELETE FROM ContactItem")
fun nukeTable()
}
Dao
interface TodoDao {
#Query("SELECT * FROM TodoItem")
fun getAll(): Flow<List<TodoItem>>
#Insert
fun insertAll(vararg todos: TodoItem)
#Delete
fun delete(todo: TodoItem)
#Update
fun update(note: TodoItem)
#Query("DELETE FROM TodoItem")
fun nukeTable()
}
And I can make some of them in base interface to shrink both of them
interface BaseDao<T> {
#Insert
fun insertAll(vararg obj: T)
#Delete
fun delete(obj: T)
#Update
fun update(obj: T)
}
#Dao
interface ContactDao: BaseDao<ContactItem> {
#Query("SELECT * FROM ContactItem")
fun getAll(): Flow<List<ContactItem>>
#Query("DELETE FROM ContactItem")
fun nukeTable()
}
#Dao
interface TodoDao: BaseDao<TodoItem> {
#Query("SELECT * FROM TodoItem")
fun getAll(): Flow<List<TodoItem>>
#Query("DELETE FROM TodoItem")
fun nukeTable()
}
But you can see that both getAll and nukeTable is still there. I hope to also make it into the BaseDao. Is there any way to do that?
Recently I've decided to start using LiveData in my Room database by adding it to ado methods and then observing them in ViewModel, then even though I updated DB version after making all the changes and made a migration, it throws this error on emulator launch
2022-07-03 15:35:17.723 4866-4891/com.example... E/SQLiteLog: (1) statement aborts at 1: [ROLLBACK;] cannot rollback - no transaction is active
2022-07-03 15:35:17.724 4866-4891/com.example... E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_0
Process: com.example..., PID: 4866
java.lang.RuntimeException: Exception while computing database live data.
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
Caused by: android.database.sqlite.SQLiteException: cannot rollback - no transaction is active (code 1 SQLITE_ERROR)
at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:709)
at android.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:441)
at android.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:403)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:589)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:422)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:151)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:112)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:706)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:483)
at androidx.room.RoomDatabase.query(RoomDatabase.java:526)
at androidx.room.util.DBUtil.query(DBUtil.java:86)
at com.example...data.database.NoteDao_Impl$16.call(NoteDao_Impl.java:723)
at com.example...data.database.NoteDao_Impl$16.call(NoteDao_Impl.java:720)
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
My dao code :
#Dao
interface NoteDao {
#Delete
fun deleteFolder(folder: Folder)
#Query("UPDATE Note SET isStarred = :state WHERE id = :id")
fun updateNoteChecked(id : Int, state : Boolean)
#Query("DELETE FROM Note WHERE id = :id")
fun deleteNoteById(id : Int)
#Query("SELECT * FROM Note WHERE id = :id")
fun getNoteById(id : Int) : Note
#Query("SELECT title FROM Folder WHERE id = :id")
fun getFolderTitleById(id : Int) : String
#Query("SELECT * FROM folder WHERE id = :id")
fun getFolderById(id : Int) : Folder
#Query("UPDATE Folder SET title = :title WHERE id = :id")
fun updateFolderTitle(id : Int, title : String)
#Query("SELECT * FROM Folder WHERE id = :id")
fun getFolderWithNotesByFolderId(id : Int) : FolderWithNotes
#Query("SELECT * FROM Note WHERE folderId = :id ORDER BY isStarred")
fun getNotesByFolderId(id : Int) : LiveData<List<Note>>
#Query("SELECT * FROM note WHERE content LIKE :query ORDER BY isStarred")
fun searchAllNotes(query : String?) : LiveData<List<Note>>
#Query("SELECT * FROM note WHERE folderId = :id AND content LIKE :query ORDER BY isStarred" )
fun searchNotesByFolderId(query : String?, id : Int) : LiveData<List<Note>>
#Query("SELECT * FROM Note ORDER BY isStarred")
fun getAllNotes() : LiveData<List<Note>>
#Query("SELECT * FROM Folder WHERE id = :id")
fun getFolderWithNotes(id : Int) : List<FolderWithNotes>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFolder(folder : Folder)
#Query("SELECT * FROM Folder")
fun getAllFolders() : LiveData<List<Folder>>
#Query("UPDATE folder SET noteCount = noteCount + 1 WHERE id = :id")
fun updateOnInsertNote(id : Int)
#Query("UPDATE folder SET noteCount = noteCount - 1 WHERE id = :id")
fun updateOnDeleteNote(id : Int)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertNote(note : Note)
#Delete
suspend fun deleteNote(note : Note)
}
Database class :
#Database(entities = [Note::class, Folder::class] , version = 11 )
abstract class NoteDatabase : RoomDatabase() {
abstract fun NoteDao() : NoteDao
}
Data base hilt provide method :
#Provides
#Singleton
fun provideNoteDatabase(app : Application) : NoteDatabase{
return Room.databaseBuilder(
app ,
NoteDatabase::class.java,
"notes",
).fallbackToDestructiveMigration()
.addCallback(DatabaseCallback())
.build()
}
As per my comment, I suspect that the issue is within the callback (DatabaseCallback) and probably in the overidden onOpen method.
The reasoning is that the trace shows:-
that you have issued a query as per at androidx.room.RoomDatabase.query(RoomDatabase.java:526)
that this successfully opens the database as per at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
-and then appears to end a transaction (without committing the transaction). As Room is proven, then the most likely issue is that an attempt is being made to end a transaction when no transaction has been started and thus, due to the lack of the commit, there is nowhere/nothing to rollback to.
I am complete new to kotlin, and trying to build a notes app using MVVM architecture. But somewhere I go wrong and end up with this error. My kotlin version is 1.5.31
NoteDao.java:11: error: Not sure how to handle insert method's return type.
public abstract java.lang.Object insert(#org.jetbrains.annotations.NotNull()
NoteDao.kt
#Dao
interface NoteDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(note:Note): Note
#Delete
suspend fun delete(note: Note): Note
#Query("SELECT * FROM notes_table ORDER BY primary_key ASC")
fun getAllNotes(): LiveData<List<Note>>
}
Note.kt
#Entity(tableName = "notes_table")
class Note(#ColumnInfo(name = "note_text", typeAffinity = TEXT) val text: String){
#PrimaryKey(autoGenerate = true) #ColumnInfo(name="primary_key", typeAffinity = INTEGER) var id = 0
}
I am not sure how to handle this.
Try doing this:
#Dao
abstract class BaseDao<T : BaseEntity>(private val tableName: String) {
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(entity: T): Long
#Update
abstract suspend fun update(entity: T)
#Delete
abstract suspend fun delete(entity: T)
}
abstract class BaseEntity {
abstract val id: Long
}
in my Android app I use MVVM model with repositories
most of my DAO and Repositories classes are almost identical, so I figured I could reuse some code by creating generic equivalents that are later extended
this worked fine for my DAO classes:
#Dao
interface BaseDao<T> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg entity: T)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertList(entities: List<T>)
#Update
suspend fun update(entity: T)
#Delete
suspend fun delete(entity: T)
}
#Dao
interface SettingDao : BaseDao<Setting> {
#Query("SELECT value FROM settings WHERE id = :settingName")
fun get(settingName: String): LiveData<String>
#Query("SELECT * FROM settings ORDER BY id ASC")
fun getAll(): LiveData<List<Setting>>
#Query("DELETE FROM settings")
suspend fun deleteAll()
}
now I want to do something similar for repository, but I cannot figure out how
this is what I've tried (I get error java.lang.ClassCastException: java.lang.Object[] cannot be cast to: Setting[]) in BaseRepository$insert$2.invokeSuspend
abstract class BaseRepository<T>(private val baseDao: BaseDao<T>) {
suspend fun update(entity: T) {
withContext(Dispatchers.IO) { baseDao.update(entity) }
}
suspend fun insert(entity: T) {
withContext(Dispatchers.IO) { baseDao.insert(entity) }
}
suspend fun delete(entity: T) {
withContext(Dispatchers.IO) { baseDao.delete(entity) }
}
}
class SettingRepository(private val settingDao: SettingDao) : BaseRepository<Setting>(settingDao) {
val company = settingDao.get("company")
val ip = settingDao.get("ip")
suspend fun deleteAll() {
withContext(Dispatchers.IO) { settingDao.deleteAll() }
}
}
My intuition is that I have to adjust this bit private val baseDao: BaseDao<T> in BaseRepository, but I cannot figure out how
And 2nd question - is there a way to get a generic table name in DAO, so I could put
#Query("DELETE FROM settings")
suspend fun deleteAll()
in the generic interface as well (in this case replace settings with entity's tableName
I am using RoomDB with Coroutines. My code looks like below -
#Dao
interface AccountDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAccountData(accountModel: AccountModel)
#Query("DELETE FROM accountTable")
suspend fun deleteAccountData()
#Query("SELECT * FROM accountTable")
suspend fun getAccountData(): Deferred<AccountModel>
}
//From my class
override suspend fun retrieveAccountData(): AccountModel {
return accountDao.getAccountData().await()
}
How do I return or what do I return for insert or delete from the DAO so that I know insert or delete was successful?
If the #Insert method receives only 1 parameter, it can return a Long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return Long[] or List<Long> instead.
#Delete method returns an Int indicating the number of rows removed from the database.
So, your Dao should be like following:
#Dao
interface AccountDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAccountData(accountModel: AccountModel): Long
#Delete
suspend fun deleteAccountData(accountModel: AccountModel): Int
#Query("SELECT * FROM accountTable")
suspend fun getAccountData(): Deferred<AccountModel>
}