How to get last value from Room using Kotlin? - android

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)
}

Related

In android flow + room, How to monitor a List of Flow

Helloo,
----DAO---
#Transaction
#Query("select * from expenses where date= :date")
suspend fun getExpenseWithTagByDate(date: LocalDate): Flow<List<ExpenseWithTag>>
I have a dao code like below. BUT if I need query two days or more, How can I do it?
fun getEnpenseWithTagByMonth(dates: List<LocalDate>) =
flow {
val lists = arrayListOf<Flow<List<ExpenseWithTag>>>()
dates.forEach {
val expenses = expenseDao.getExpenseWithTagByDate(it)
lists.add(expenses)
}
emit(lists)
}.flowOn(Dispatchers.IO)
then
getEnpenseWithTagByMonth(dates).asLiveData()
finally i get a type, and it's very terrible:
ArrayList<Flow<List<ExpenseWithTag>>>>>
How I can write the code with Concise and efficient
To answer your question
if I need query two days or more, How can I do it?
You could use an in query, and then group them.
#Query("select * from expenses where date IN (:dates)")
suspend fun getExpenseWithTagByDate(dates: List<LocalDate>): Flow<List<ExpenseWithTag>>
fun getEnpenseWithTagByMonth(dates: List<LocalDate>): Flow<List<List<ExpenseWithTag>>> {
return expenseDao.getExpenseWithTagByDate(dates).map { expenses ->
expenses.groupBy{ expense -> expense.date }.values
}
}

Sorting list with repository and ViewModel

First time adding an object (Deck) is added invisibly. It will only appear once the sorting method has been chosen from context menu. However, this has to be repeated each time for the screen to be updated.
The issue should lie within the repository as getAllDecks references allDecks. It is as if allDecks is not updating or not realising its .value is changing as allDecks.postValue() intakes the List<Deck> from database. This isn't a LiveData<List<Deck>>, it only does a one time thing. How to make repository reading constant updates from database?
I am trying to sort a list stored in the repository. My ViewModel has a List<obj> referencing items in repository. Sorting occurs when a user presses a context menu item. This action isn't working. The debugging tool showed repository method being called and things were reassigned. This should have worked as ViewModel was referencing the repository and MainActivity would automatically update if the list changed.
MainActivity context menu opens and reacts to onClick sorting changes. Thus it is called. I also know since delete and insert queries are working that MainActivity is listening to the ViewModel's changing list. What am I doing wrong? Also, how to debug database queries (when debugger transitions to viewing SQLite query it gets in a loop)?
Main Activity (abbreviated) :
globalViewModel.sortBy(Sort.ALPHA_ASC) //Set default sorting
//Listen for livedata changes in ViewModel. if there is, update recycler view
globalViewModel.allDecks.observe(this, Observer { deck ->
deck?.let { adapter.setDecks(deck) }
})
override fun onContextItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.sort_by_alpha_asc -> { globalViewModel.sortBy(Sort.ALPHA_ASC) ; currentSort = Sort.ALPHA_ASC ; contextMenuText.setText(R.string.sort_by_alpha_asc) ; return true; }
R.id.sort_by_alpha_desc -> { globalViewModel.sortBy(Sort.ALPHA_DES) ; currentSort = Sort.ALPHA_DES ; contextMenuText.setText(R.string.sort_by_alpha_des) ; return true; }
R.id.sort_by_completed_hidden -> { globalViewModel.sortBy(Sort.NON_COM) ; currentSort = Sort.NON_COM ; contextMenuText.setText(R.string.sort_by_non_complete) ; return true; }
R.id.sort_by_due_date -> { globalViewModel.sortBy(Sort.DUE_DATE) ; currentSort = Sort.DUE_DATE ; contextMenuText.setText(R.string.sort_by_due_date) ; return true; }
else -> return super.onContextItemSelected(item)
}
}
View Model :
private val repository: DeckRepository
val allDecks: LiveData<List<Deck>>
init {
val decksDao = FlashCardDB.getDatabase(application, viewModelScope).DeckDAO()
repository = DeckRepository(deckDao = decksDao)
allDecks = repository.getAllDecks()
}
fun sortBy(sortMethod: Sort) = viewModelScope.launch(Dispatchers.IO) {
when (sortMethod) {
Sort.ALPHA_ASC -> repository.sortBy(Sort.ALPHA_ASC)
Sort.ALPHA_DES -> repository.sortBy(Sort.ALPHA_DES)
Sort.NON_COM -> repository.sortBy(Sort.NON_COM)
Sort.DUE_DATE -> repository.sortBy(Sort.DUE_DATE)
}
}
DeckRepository :
private var allDecks = MutableLiveData<List<Deck>>() //instantiate object
fun getAllDecks(): LiveData<List<Deck>> = allDecks //Repository handles livedata transmission. ViewModel references the actual Data.
suspend fun sortBy(sortingMethod: Sort) {
when (sortingMethod) {
Sort.ALPHA_ASC -> allDecks.postValue(deckDao.getDecksSortedByAlphaAsc())
Sort.ALPHA_DES -> allDecks.postValue(deckDao.getDecksSortedByAlphaDesc())
Sort.NON_COM -> allDecks.postValue(deckDao.getDecksSortedByNonCompleted())
Sort.DUE_DATE -> allDecks.postValue(deckDao.getDecksSortedByDueDate())
}
}
suspend fun insert(deck: Deck) {
deckDao.insert(deck)
}
Database :
//Sorting
#Query("SELECT * from deck_table ORDER BY title ASC")
fun getDecksSortedByAlphaAsc(): List<Deck>
#Query("SELECT * from deck_table ORDER BY title DESC")
fun getDecksSortedByAlphaDesc(): List<Deck>
#Query("SELECT * from deck_table WHERE completed=1 ORDER BY title ASC")
fun getDecksSortedByNonCompleted(): List<Deck>
#Query("SELECT * from deck_table ORDER BY date ASC")
fun getDecksSortedByDueDate(): List<Deck>
//Modifying
#Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(deck: Deck)
Your activity isn't showing the changes in real time because your repository reassigns its LiveData. While your approach of having a single LiveData to be observed by the activity is correct, you should actually change only its value, not the reference if that makes sense.
Here's an example:
Repository
private val allDecks = MutableLiveData<List<Deck>>()
fun getAllDecks(): LiveData<List<Deck>> = allDecks
fun sortBy(sortingMethod: Sort) {
when (sortingMethod) {
/* If you're handling your DB operations with coroutines, this function
* should be suspendable and you should set the value to allDecks
* with postValue
*/
Sort.ALPHA_ASC -> allDecks.value = deckDao.getDecksSortedByAlphaAsc()
Sort.ALPHA_DES -> allDecks.value = deckDao.getDecksSortedByAlphaDesc()
Sort.NON_COM -> allDecks.value = deckDao.getDecksSortedByNonCompleted()
Sort.DUE_DATE -> allDecks.value = deckDao.getDecksSortedByDueDate()
}
}
Consequently, your DAO queries will no longer return LiveData, but the lists themselves:
DAO
#Query("SELECT * from deck_table ORDER BY title ASC")
fun getDecksSortedByAlphaAsc(): List<Deck>
#Query("SELECT * from deck_table ORDER BY title DESC")
fun getDecksSortedByAlphaDesc(): List<Deck>
#Query("SELECT * from deck_table WHERE completed=1 ORDER BY title ASC")
fun getDecksSortedByNonCompleted(): List<Deck>
#Query("SELECT * from deck_table ORDER BY date ASC")
fun getDecksSortedByDueDate(): List<Deck>
The problem is in DAO's query. It returns List, witch is not Flow or LiveData, thus it can't be observed and react to changes in list... It should be like this:
#Query("SELECT * from deck_table ORDER BY title ASC")
fun getDecksSortedByAlphaAsc(): Flow<List<Deck>>
#Query("SELECT * from deck_table ORDER BY title DESC")
fun getDecksSortedByAlphaDesc(): Flow<List<Deck>>
#Query("SELECT * from deck_table WHERE completed=1 ORDER BY title ASC")
fun getDecksSortedByNonCompleted(): Flow<List<Deck>>
#Query("SELECT * from deck_table ORDER BY date ASC")
fun getDecksSortedByDueDate(): Flow<List<Deck>>
Now you can iplement allDecks variable witch will be LiveData<List> using flow and Transformations. Something like this:
val sortingMethod: LiveData<Sort> = "your sorting type here"
val allDecks: LiveData<List<Deck>> = Transformations.switchMap(sortingMethod) {
when (sortingMethod) {
Sort.ALPHA_ASC -> allDecks.value = deckDao.getDecksSortedByAlphaAsc().asLiveData()
Sort.ALPHA_DES -> allDecks.value = deckDao.getDecksSortedByAlphaDesc().asLiveData()
Sort.NON_COM -> allDecks.value = deckDao.getDecksSortedByNonCompleted().asLiveData()
Sort.DUE_DATE -> allDecks.value = deckDao.getDecksSortedByDueDate().asLiveData()
}
Whats happens is we observe liveData sorting method and change our allDecks variable every time when it changes.
After this you just need to observe allDecks and submit it to recycler view adapter every time it changes.
Something like this:
viewModel.allDecks.observe(this.viewLifecycleOwner) {
adapter.submitList(it)
}
P.S. RecyclerViews adapter must be implemented using DiffCallback so it can change every time list is changed...

How to get multiple counts from room database?

I want to return multiple counts from Room Select query in android.
My query is like
Select Count(CASE WHEN x = '0' or x = '2'), Count(Case when a = '33' or a = '23') FROM my_table WHERE id=10
I want above query to return something as list which will contain values of both the above Count() function. This can be easily done using SQLite but I want to use it in room.
You can give names to the counts and return them as a class, like:
data class MyCounts(
#ColumnInfo(name = "first_count")
val firstCount: Int,
#ColumnInfo(name = "second_count")
val secondCount: Int
)
#Dao
interface MyDao {
#Query("SELECT COUNT(*) as first_count, COUNT(*) as second_count from my_table")
suspend fun getMyCounts(): MyCounts
}

Access and Delete a row in one query in SQLite

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
}

How to test Dao methods which return DataSource.Factory?

After shifting from SqliteOpenHelper to room in my app, I've trying to write tests for the DAO class.
My DAO looks something like this:
#Query("SELECT * FROM cards")
fun getAllCards(): List<CardData>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCard(vararg cardData: CardData): List<Long>
#Query("SELECT * FROM cards ORDER BY isRead ASC, id DESC")
fun getItemList(): DataSource.Factory<Int, CardData>
#Query("SELECT * FROM cards where instr(title, :query) > 0 ORDER BY isRead ASC, id DESC")
fun getItemList(query: String): DataSource.Factory<Int, CardData>
#Query("UPDATE cards set isRead = 1 where title = :title")
fun markRead(title: String): Int
While writing test for getAllCards, insertCard and markRead is trivial, I am still not sure how do I test the apis which return DataSource.Factory, i.e getItemList apis.
After searching on internet, I couldn't find anything related to this.
Can someone please help.
this is how I did:
val factory = dao.getItemList()
val list = (factory.create() as LimitOffsetDataSource).loadRange(0, 10)
Quoting CommonsWare
If you use paging with Room and have a #Dao method return a DataSource.Factory, the generated code uses an internal class named LimitOffsetDataSource to perform the SQLite operations and fulfill the PositionalDataSource contract.
source: https://commonsware.com/AndroidArch/previews/paging-beyond-room#head206

Categories

Resources