I am using Room Database to make a database to store information in a table. I want to access one entry from the table and delete the same entry without the need to call two functions.
#Query("SELECT * FROM history_packet_table ORDER BY timestamp ASC LIMIT 1")
fun get(): HistoryPacket?
#Query("DELETE FROM history_packet_table ORDER BY timestamp ASC LIMIT 1")
fun delete()
I want these two operations to happen only by calling get. Is there a way?
I believe that you can add the following to the Dao :-
#Transaction
fun getAndDelete() {
get()
delete()
}
Obviously you can call the function what you wish. However, the get seems to be useless as it is.
So you may want something like :-
#Query("SELECT * FROM history_packet_table WHERE timestamp = (SELECT min(timestamp) FROM history_packet_table)")
fun get() :HistoryPacketTable
#Query("DELETE FROM history_packet_table WHERE timestamp = (SELECT min(timestamp) FROM history_packet_table)")
fun delete() :Int
#Transaction
fun getAndDelete() :HistoryPacketTable {
// Anything inside this method runs in a single transaction.
var rv: HistoryPacketTable = get()
val rowsDeleted: Int = delete()
if (rowsDeleted < 1) {
rv = HistoryPacketTable();
//....... set values of rv to indicate not deleted if needed
}
return rv
}
Note as LIMIT on delete is turned off by default, the queries can be as above, this assumes that timestamp is unique otherwise multiple rows may be deleted, in which case the Dao could be something like
:-
#Delete
fun delete(historyPacketTable: HistoryPacketTable) :Int
#Transaction
fun getAndDelete() :HistoryPacketTable {
// Anything inside this method runs in a single transaction.
var rv: HistoryPacketTable = get()
val rowsDeleted: Int = delete(rv)
if (rowsDeleted < 1) {
rv = HistoryPacketTable();
//....... set values to indicate not deleted
}
return rv
}
Related
I am trying to get the user ID from the newest user. How can I make the insert method spit the ID when the ID is autogenerated?
in Model
#PrimaryKey(autoGenerate = true)
val userId: Int
in Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun addUserWithLong(user: User): LiveData<Long>
in Repository
fun addUserWitLong(user: User): LiveData<Long> {
return userDao.addUserWithLong(user)
}
in ViewModel
fun addUserWithLong(user: User): LiveData<Long> {
return repository.addUserWitLong(user)
}
in Fragment
val id: Long? = userViewModel.addUserWithLong(user).value
I have read in the docs that #Insert returns Long as the row ID but I do not know how to program it. Now the error is "Not sure how handle insert method return type." Is there some way to make with LiveData and not with Rxjava. That is without the need to download more dependecies.
As per the documentation here
If the #Insert method receives a single parameter, it can return a
long value, which is the new rowId for the inserted item. If the
parameter is an array or a collection, then the method should return
an array or a collection of long values instead, with each value as
the rowId for one of the inserted items. To learn more about returning
rowId values, see the reference documentation for the #Insert
annotation, as well as the SQLite documentation for rowid tables
So you can use it like
#Insert(onConflict = OnConflictStrategy.REPLACE)
long addUserWithLong(user: User)
or if you are inserting a list
#Insert(onConflict = OnConflictStrategy.REPLACE)
long[] addUserWithLong(user: List<User>)
Edit-1
After checking answers from this post.
No, you can't. I wrote an answer to the issue. The reason is, that
LiveData is used to notify for changes. Insert, Update, Delete won't
trigger a change.
I just created a test project and successfully received Id of last inserted item in activity. Here is my implementation.
Dao
#Insert
suspend fun addUser(user: Users): Long
Repo
suspend fun insertUser(context: Context, users: Users): Long {
val db = AppDatabase.getInstance(context)
val dao = db.userDao()
return dao.addUser(users)
}
ViewModel
fun addUser(context: Context, users: Users) = liveData {
//you can also emit your customized object here.
emit("Inserting...")
try {
val userRepo = UsersRepo()
val response = userRepo.insertUser(context, users)
emit(response)
} catch (e: Exception) {
e.printStackTrace()
emit(e.message)
}
}
Activity
viewModel.addUser(applicationContext, user).observe(this, Observer { userId ->
Log.d("MainActivity", "Inserted User Id is $userId")
})
Check test application here.
How should i return only one value from Room?
I've made my query in the DAO like:
#Query("SELECT * FROM testata WHERE id = (SELECT MAX(id) FROM testata)")
fun selectLast(): Testata
Which should return the last insert row, then in my repository i've done the following:
#WorkerThread
suspend fun lastTestata(): Testata {
return testataDAO.selectLast()
}
And in the ViewModal i was trying the following:
fun lastTestata(): Testata = viewModelScope.launch {
return repository.lastTestata()
}
But instead it requires Job instead of testata in ViewModal fun lastTestata() so what is the right way to get single values from room in android?
If your id is incremental(should be) you can use the limit to get one register, like this:
SELECT * FROM testata ORDER BY ID DESC LIMIT 1
The query "SELECT * FROM testata WHERE id = (SELECT MAX(id) FROM testata)" will return a list. So you can write something like
#Query("SELECT * FROM testata WHERE id = (SELECT MAX(id) FROM testata)")
fun selectLast(): List<Testata>
And in your repo you can write
#WorkerThread
suspend fun lastTestata(): Testata {
return testataDAO.selectLast().get(0)
}
I want to know that how can i sum value in the room database .
Let say i have insert this value (10,000) in the room database and i want to insert new value to database (20,000) and i want the new value to be sum with old value not replace it .
how can i do that .?
example of code :
Database Table :
#entity
class sum (
id : int ,
value : Long )
Dao :
#insert (onConflict = OnConflictStrategy.REPLACE)
suspend fun insert (model :sum)
what shoud i use instead of above insert .
#Query("SELECT * FROM sum")
fun getall () : LiveData<sum>
and in activity :
late init var : viewmodel : Viewmodeldatabase
val textvalue : TextView
viewmodel = Viewmodelprovider(this).get(Viewmodeldatabase::class.java)
textvalue = findvidwbyid(R.id.text)
viewmodel.insert(sum(1 , 10000)
// when the above value insert , if there is an old value sum with it and not replace it or remove it
viewmodel.getall.observe(this , Observer {
textvalue.text = it.value
)}
thank's guys for watch my code and help me .
Try to add next method in your dao:
#Query("UPDATE sum SET value = value + :addValue WHERE id =:id")
suspend fun updateSum(id: Int, addValue: Long)
Then you can call it from your ViewModel/Activity
UPDATE
For single method for update/insert put these methods in dao:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSum(model: Sum)
#Query("UPDATE sum SET value = value + :value WHERE id = :id")
suspend fun updateSum(id: Int, value: Long)
#Query("SELECT * FROM sum WHERE id = :id")
suspend fun getSumById(id: Int): Sum?
#Transaction
suspend fun updateOrInsertSum(sum: Sum) { // <-- this method updates value or insert new item, if id is not found
getSumById(sum.id)?.let { updateSum(sum.id, sum.value) } ?: insertSum(sum)
}
Of course you should add method updateOrInsertSum to your repository and viewmodel as well
Then if you call for id = '1' for the first time value will be inserted:
viewmodel.updateOrInsertSum(Sum(1 ,10000)) // <-- id = 1, value = 10000
On the next call item will be updated with the same method:
viewmodel.updateOrInsertSum(Sum(1 ,20000)) // <-- id = 1, value = 10000+2000
In your Dao, I see that you use the function getAll(),
lets say that you save them into a list of integers
val list: List<Int>//list holding all of you data returned in the onChanged method of the observer callback(or the lambda callback in case of Kotlin)
lets say that you want to update the value at position 1:
#Update
public void update(newVal: Int);
and when calling the update function pass to it the summation of the old value coming from the "list" I mentioned earlier and the newValue
database.dao.update(list[1] + newValue)
I would like to perform an asynchonous operation on each record in a large Room table.
I thought I could add a method returning Flow in my DAO like this:
#Query("SELECT * FROM events")
fun getEvents(): Flow<EventEntity>
But according to this blog post and this documentation page returning a Flow is making an "observable read" so the Flow never completes and it watches for database changes.
My goal is to iterate over all the entities only once. I don't want the "observability" behavior. Also, since the table is very large, I don't want to load all the records into a List at once in order to avoid consuming too much memory.
Could you recommend some solution, please?
Create a new method that does not use Flow.
#Query("SELECT id FROM events")
fun getAllIds(): List<Int> // If your primary key is Integer.
#Query("SELECT * FROM events WHERE id = :id")
fun getById(id: Int): EventEntity?
Use Kotlin coroutines to call this method on IO thread.
There could be several strategies to load one row at a time. This is the simplest - get all ids and load each item one at a time.
suspend fun getEvents() {
withContext(Dispatchers.IO) {
// Get entities from database on IO thread.
val ids = dao.getAllIds()
ids.forEach { id ->
val event = dao.getById(id)
}
}
}
Pagination based approach
This approach assumes that you have a column that stores timestamp (eg. created_at).
#Query("SELECT * from events WHERE created_at > :timestamp ORDER BY created_at LIMIT 10")
fun getAfter(timestamp: Long): List<EventEntity>
You can use this method to paginate.
suspend fun getEvents() {
withContext(Dispatchers.IO) {
var timestamp: Long = 0
while (true) {
// Get entities from database on IO thread.
val events = dao.getAfter(timestamp)
// Process this batch of events
// Get timestamp for pagination offset.
timestamp = events.maxBy { it.createAt }?.createAt ?: -1
if (timestamp == -1) {
// break the loop. This will be -1 only if the list of events are empty.
}
}
}
}
I am using room database to store comments and RxJava as a listener to do some stuff when the database is changed.
I want to not call the callback when delete is called on the table, only when insert is called.
What i found out so far is that Room library has triggers that are called on delete, insert and update of the table that in turn call RxJava's methods.
Is there any way to drop the delete trigger and get callbacks only for the insert and update methods?
Here is my CommentDAO:
#Query("SELECT * FROM comments" )
fun getAll(): Flowable<List<Comment>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(comment: Comment)
#Delete
fun delete(comment: Comment)
And my RxJava callback functions:
/**
* Inserts comment into comment database
*
* #param object that's going to be inserted to the database
*/
fun saveComment(comment: Comment) {
Observable.just(comment).subscribeOn(Schedulers.io()).map({ comment1 -> commentdb.commentDao().insert(comment1) }).subscribe()
}
/**
* Removes comment from the database
*
* #param comment object that's going to be removed
*/
fun removeComment(comment: Comment){
Observable.just(comment).subscribeOn(Schedulers.io()).map({ comment1 -> commentdb.commentDao().delete(comment1) }).subscribe()
}
fun createCommentObservable(uploader: CommentUploader) {
commentdb.commentDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(
{
success -> uploader.queue(success)
}
)
}
You can get a Flowable<List<Comment>> that only emits on insertions and not on deletions by filtering the original getAll() Flowable so that only those List<Comment> items are passed through that contain more Comments than the previous List<Comment>.
You can implement this filtering with the following transformations:
Prepend the flowable with an empty list so that we have a baseline for insertions.
Get RxJava window()s of size 2, so that we will be able to compare adjacent items.
window() returns Flowable<Flowable<Comment>>. Convert it to Flowable<List<Comment>> with flatMap() and toList() on the inner Flowable.
Filter those 2-element windows that represent an insertion (the size of the first element is less than the size of the second).
Emit only the 2nd element of the filtered windows.
In Kotlin:
fun getAllAfterInsertions() {
getAll()
.startWith(emptyList<String>()) // (1)
.window(2, 1) // (2)
.flatMap({ w -> w.toList().toFlowable() }) // (3)
.filter({ w -> w.size == 2 && w[0].size < w[1].size }) // (4)
.map({ window -> window[1] }) // (5)
}
To delete without notification I simply replace
MyDao().delete()
with one executing a #Query
MyDao().deleteLast()
then thew Flowable doesn't emit a new event. The #Dao looks like this
#Dao
abstract class MyDao : BaseDao<Data> {
#Query("DELETE FROM Data WHERE id = (select min(id) from Data)") // or something else
abstract fun deleteLast()
#Delete
fun delete(data: Data)
}