Room does not update livedata of list from a subset - android

I have a database that has a table with multiple columns, and I wish to only load 3 when showing my list and before the user selects one item to show it.
In order to do this I have created the following dao
#Dao
interface ReportDao {
#Query("SELECT id, name, description, created FROM reports ORDER BY created DESC")
fun loadShortReports(): LiveData<List<ReportItemDO>>
#Insert(onConflict = OnConflictStrategy.FAIL)
fun saveReports(reports: List<ReportDO>): Completable
}
this is my report entity:
#Entity(tableName = "reports")
data class ReportDO(
#PrimaryKey val id: UUID
, val name: String
, val description: String
.......)
and this is the minified entity for the list
data class ReportItemDO(
override val id: UUID
, override val name: String
, override val description: String
, #ColumnInfo(name = "created") override val date: Date)
: ReportItemVO
(the ReportItemVO interface is what the list expects)
When the application loads, everything loads correctly
but in order to test this I created a simple button that adds a random ReportDO item to the database using this code:
repository.addReport(
report
).subscribeOn(schedulers.subscribeScheduler)
.observeOn(schedulers.observeScheduler)
.subscribe {
Log.i(MainViewModel.TAG, "success")
}
which in turn calls
reportDao.saveReports(listof(report))
but the list does not seem to get updated automatically
When I close and restart the application the list is updated
I am assuming this is because I'm only taking a subset of the entity so Room does not know it should trigger the update
Is there a way to make Room understand that the LiveData it created was updated and trigger the loading process again?
Also , because I wish to add a refresh button, is there a way to send Room a message to manually reload the data?

Related

how to query a room database by row android kotlin

I am new to Room and it's throwing me through a loop. I am trying to take a specific row of a specific data class in my Room database and compile into a list and turn this list into a String so I can do stuff with it. I can sort of do this by printing the contents of the Room database table to the console, but that's it.
I'll show you my code that I have
this is my data class.
code.kt
#Parcelize
#Entity(tableName = "code_table")
data class code(
#PrimaryKey(autoGenerate = true)
val id: Int,
val code: String //this is the item I want to isolate
): Parcelable
a snippet of my ViewModel.kt that I use.
val readAllData: LiveData<List<code>>
private val repository: respository
init {
val Dao = database.getDatabase(application).dao()
repository = respository(Dao)
readAllData = repository.readAllData
}
a snippet of my repository.kt that I use.
val readAllData: LiveData<List<code>> = dao.readAllData()
a snippet of my Dao.kt that I use.
#Query("SELECT * FROM code_table ORDER BY id ASC")
fun readAllData(): LiveData<List<code>>
How I read the Room database table
private fun dbread(){
mUserViewModel = ViewModelProvider(this).get(ViewModel::class.java)
mUserViewModel.readAllData.observe(viewLifecycleOwner, Observer { user ->
var abc = user
println(abc)
})
}
this is what it outputs
I/System.out: [code(id=2, code=textA), code(id=3, code=textB), code(id=4, code=textC)]
I am not hell-bent on only querying the code row but I need to aggregate all the data in the code row into a string or JSON array. so, in this case, that would be the textA, textB and textC (I'm worried I haven't made myself clear)
I have also tried doing the following in the Dao
#Query("SELECT code FROM code_table")
fun readdb(): LiveData<List<code>>//I've tried this with lot different types within the Parentheses
this makes the build fail: it says the following
:app:kaptDebugKotlin 1 error
java.lang.reflect.InvocationTargetException (no error message)
I would guesstimate that this error is because the SQL syntax is off but I don't think it is.
I have also tried messing around with the ViewModel and repository to see if I can get them to only output just the code row but to no avail. I'm surprised I'm the first to post about this.
thank you for your time.
You can obtain you desidered result just by using SQL.
In Android (and with Room) you're using a SQLite database, so the most important thing is writing some SQL that is compliant with SQLite.
Then you can select the concatenation of all the values in your table, just by asking to the database to do it. You can achieve it with this:
#Query("SELECT GROUP_CONCAT(code) FROM code_table")
fun readConcatenatedCode(): LiveData<String>
The returned value will be a LiveData of String, and the String will contain all the values, concatenated.

Android PagingLibrary network + database

I'm working on Messaging app and I am implementing database + network to save chat messages from api and show them from database. I'm using BoundaryCallback to fetch message when database has no more data. My api works like this:
getlist( #Query("msgid") long msgid,
#Query("loadolder") boolean olderOrNewer,
#Query("showcurrentMessage") boolean showcurrentMessage,
#Query("MsgCountToLoad") int MsgCountToLoad);
msgid : last message id of that chat . if db is empty I request
with the chat.getlastmessageid if the db has data but there was no
more data I will send last message id in db to load more and if first
time opening chat, the last message id in db was not equal to
chat.lastmessageid it's mean there is new message to load.
loadolder : this flag false to tell api load new messages from this
message id I sent to you and on and if flag set to true means load
older messages from this message id I sent to you
showcurrentMessage: if true it will give me the current message (msgid) too
MsgCountToLoad : how many messages to take from api
question is how to handle this stuff in Pagginglibrary? How to tell it to load older or newer message which is based on scrolling position. First time to load data is easy, it will returns null object so I will use the chat.lastmessageid next time opening chat where I could check if chat.lastmessageid is equal to db.lastmessageid and tell it to load more new messages.
PagedList.BoundaryCallback has two separate APIs for prepending and appending.
You should look to implement these methods:
onItemAtEndLoaded
onItemAtFrontLoaded
Assuming your initial load loads the most recent messages, and scrolling up loads older messages, you can just pass true for loadolder in onItemAtFrontLoaded and false in onItemAtEndLoaded.
I'm working my last project on Messaging app. One the most important and common things that we do in our projects is to load data gradually from the network or the database maybe because there is a huge number of entities that can’t be loaded at once.
If you are not familiar with paging library or live data concepts please take your time to study them first because I’m not going to talk about them here. There are lots of resources you can use to learn them.
My solution consists of two main parts!
Observing the database using Paging Library.
Observing the RecyclerView to understand when to request the server for data pages.
For demonstration we are going to use an entity class that represents a Person:
#Entity(tableName = "persons")
data class Person(
#ColumnInfo(name = "id") #PrimaryKey val id: Long,
#ColumnInfo(name = "name") val name: String,
#ColumnInfo(name = "update_time") val updateTime: Long
)
1. Observe the database
Lets start with the first and easier one:
To observe the database we are going to define a method in our dao that returns a DataSource.Factory<Int, Person>
#Dao
interface PersonDao {
#Query("SELECT * FROM persons ORDER BY update_time DESC")
fun selectPaged(): DataSource.Factory<Int, Person>
}
And now in our ViewModel we are going to build a PagedList from our factory
class PersonsViewModel(private val dao: PersonDao) : ViewModel() {
val pagedListLiveData : LiveData<PagedList<Person>> by lazy {
val dataSourceFactory = personDao.selectPaged()
val config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.build()
LivePagedListBuilder(dataSourceFactory, config).build()
}
}
And from our view we can observe the paged list
class PersonsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_persons)
viewModel.pagedListLiveData.observe(this, Observer{
pagedListAdapter.submitList(it)
})
}
}
Alright, now that is basically what we should do for the first part. Please notice that we are using a PagedListAdapter. Also we can do some more customization on our PagedList.Config object but for simplicity we omit it. Again please notice that we didn’t use a BoundaryCallback on our LivePagedListBuilder.
2. Observe the RecyclerView
Basically what we should do here is to observe the list, and based on where in the list we are right now, ask the server the provide us with the corresponding page of data. For observing the RecyclerView position we are going to use a simple library called Paginate.
class PersonsActivity : AppCompatActivity(), Paginate.Callbacks {
private var page = 0
private var isLoading = false
private var hasLoadedAllItems = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_persons)
viewModel.pagedListLiveData.observe(this, Observer{
pagedListAdapter.submitList(it)
})
Paginate.with(recyclerView, this).build()
}
override fun onLoadMore() {
// send the request to get corresponding page
}
override fun isLoading(): Boolean = isLoading
override fun hasLoadedAllItems(): Boolean = hasLoadedAllItems
}
As you can see we bound the Paginate with the recycler view and now we have three callbacks. isLoading() should return the state of network. hasLoadedAllItems() shows whether or not we have reached the last page and there is no more data to load from the server. Most of what we do is implementing the last method onLoadMore().
In this stage we should do three things:
Based on the recyclerView position, we ask the server to present us with the right data page.
Using the fresh data from the server we update the database, resulting in updating the PagedList and showing the fresh data. Don’t forget we are observing the database!
If the request fails we show the error.
With these simple steps we solve two problems.
First of all despite the BoundaryCallbak, that doesn’t have a callback to fetch the already fetched data, we are requesting each page on demand so we can notice the updated entities and also update our own local databse.
Second we can easily show the state of the network and also show the possible network failures.
Sounds fine right? Well we haven’t solved one particular problem yet. And that is what if one entity gets deleted from the remote server. How are we going to notice that! Well that is where ordering of data comes in. With a really old trick of sorting the data we can notice the gaps between our persons. For example we can sort our persons based on their update_time now if the returned JSON page from the server looks like this:
{
"persons": [{
"id": 1,
"name": "Reza",
"update_time": 1535533985000
}, {
"id": 2,
"name": "Nick",
"update_time": 1535533985111
}, {
"id": 3,
"name": "Bob",
"update_time": 1535533985222
}, {
"id": 4,
"name": "Jafar",
"update_time": 1535533985333
}, {
"id": 5,
"name": "Feryal",
"update_time": 1535533985444
}],
"page": 0,
"limit": 5,
"hasLoadedAllItems": false
}
Now we can be sure that whether if there is a person in our local database that its update_time is between the first and the last person of this list, but it is not among these persons, is in fact deleted from the remote server and thus we should delete it too.
I hope I was too vague but take a look at the code below
override fun onLoadMore() {
if (!isLoading) {
isLoading = true
viewModel.loadPersons(page++).observe(this, Observer { response ->
isLoading = false
if (response.isSuccessful()) {
hasLoadedAllItems = response.data.hasLoadedAllItems
} else {
showError(response.errorBody())
}
})
}
}
But the magic happens in the ViewModel
class PersonsViewModel(
private val dao: PersonDao,
private val networkHelper: NetworkHelper
) : ViewModel() {
fun loadPersons(page: Int): LiveData<Response<Pagination<Person>>> {
val response =
MutableLiveData<Response<Pagination<Person>>>()
networkHelper.loadPersons(page) {
dao.updatePersons(
it.data.persons,
page == 0,
it.hasLoadedAllItems)
response.postValue(it)
}
return response
}
}
As you can see we emit the network result and also update our database
#Dao
interface PersonDao {
#Transaction
fun updatePersons(
persons: List<Person>,
isFirstPage: Boolean,
hasLoadedAllItems: Boolean) {
val minUpdateTime = if (hasLoadedAllItems) {
0
} else {
persons.last().updateTime
}
val maxUpdateTime = if (isFirstPage) {
Long.MAX_VALUE
} else {
persons.first().updateTime
}
deleteRange(minUpdateTime, maxUpdateTime)
insert(persons)
}
#Query("DELETE FROM persons WHERE
update_time BETWEEN
:minUpdateTime AND :maxUpdateTime")
fun deleteRange(minUpdateTime: Long, maxUpdateTime: Long)
#Insert(onConflict = REPLACE)
fun insert(persons: List<Person>)
}
Here in our dao first we delete all the persons that their updateTime is between the first and last person in the list returned from the server and then insert the list into the database. With that we made sure any person that is deleted on the server is also deleted in our local database as well. Also notice that we are rapping these two method calls inside a database #Transaction for better optimization. The changes of the database will be emitted through our PagedList thus updating the ui and with that we are done.

Android Room - best way to retrieve a record to be updated

I have an app which has a session end routine. I want to update the session with the end date/time and potentially other information when the End Session button is clicked. I have a dao, a repository, and a ViewModel.
I thought the best way to do this would be to get the record, update the fields in the object, and save the object back to Room.
I don't exactly know the best way to go about this. Here is the code I am working with:
In the Dao:
#Query("SELECT * FROM Session WHERE id=:id")
Single<Session> getSessionById(int id);
In the repository:
public Single<Session> getSessionById(int sessionId) {
return sessionDao.getSessionById(sessionId);
}
In the ViewModel:
public void endSession () {
Single<Session> session = sessionRepository.getSessionById(sessionId);
//????? session.doOnSuccess()
//get current date/time
Date date = Calendar.getInstance().getTime();
//set the end date
session.setEndTime(date);
//update the session
sessionRepository.update(session);
}
I would love any advice on the range of options. I had started using a plain object, but got errors related to running the query on the main thread and wanted to avoid that. I don't need an observable/flowable object. I understand Async is to be deprecated in Android 11. So I thought Single would work.
Any advice on my choice to use Single or code would be really helpful.
UPDATE:
I just need a simple example in Java of pulling a single record from and the main part is the functionality in the ViewModel (what does the doOnSuccess look like and optionally on error as well).
If you just want to update without retrieving the whole record, writing a custom query is the option that I go with:
#Query("UPDATE table_name SET column_name = :value WHERE id = :id")
fun update(id: Int, value: String): Completable
In repository:
fun update(id: Int, value: String): Completable
In ViewModel or UseCase:
update().subscribeOn(...).observeOn(...).subscribe()
If you want to avoid using RxJava:
#Query("UPDATE table_name SET column_name = value WHERE id = :id")
fun update(id: Int, value: String): Boolean
And use Executors to run transactions on a background thread.
Executors.newSingleThreadExecutor().execute {
repository.update()
}
If you want to perform both retrieving and updating you can use #Update and #Query to retrieve the recorded inside your ViewModel or UseCase (interactor) and push the update toward Room.
RxJava Single Example:
#Query("SELECT * FROM table_name")
fun getAll(): Single<List<Model>>
Repository:
fun getAll(): Single<List<Model>> = modelDao.getAll()
ViewModel or UseCase:
val statusLiveData = MutableLive<Strig>()
modelRepository.getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ statusLiveData.value = "Success" }, { statusLiveData.value = "Failed" })

One-shot request single result in Room Android

I am practicing my android skills (beginner) by coding a grocery list app. I have two tables in my db, a shopping_item table (The items I want to buy) and a reference_item table (The items I know the category and the unit price). Each time I add a shopping item, there is an refId field referencing to the reference item id corresponding. It is a default value to a default reference item if the shopping item is not referenced yet.
I use a MVVM model. I then have a DAO, a repository, a viewModel and my fragments that display data.
When I add a new shopping item, I want to know if there is a corresponding reference item. I want to do the following Query:
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
It returns the id of the reference item corresponding as an Int or is null if it is not referenced yet. In my repository, I have a function like that:
suspend fun getRefItem(refName : String) = db.getShoppingDao().getRefItem(refName)
For now, I think I am doing alright. No mistake in sight I guess.
The problem begin when I try to implement my viewModel. What should I do? What about my fragment?
I have a addNewItem(name: String, amount: Int) function in my fragment to add the new item. I can find the reference item corresponding with the name provided.
I tried multiple things, using LiveData, suspend functions, mutableLiveData/LiveData, but I am getting lost right now. Every tutorials or examples use LiveData or Query all data from the db. I just want one Integer, one Time, no need of LiveData I think.
here is the complete solution. Hope this is useful for you.
DAO
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
Repository
// Specify return datatype as Int
suspend fun getRefItem(refName : String): Int = db.getShoppingDao().getRefItem(refName)
ViewModel
fun getRefItem(name: String): LiveData<Int> {
val result : MutableLiveData<Int>() <-- setup livedata to return as value
viewModelScope.lanuch {
result.postValue(repository.getRefItem(name))
}
return result <-- return livedata
}
Fragment
fun addNewItem(name: String, amount: Int) {
// setup viewModel observer
viewModel.getRefItem(name).observer { viewLifecycleOwner, { value ->
// GET YOUR INT VALUE HERE
Log.i("VALUE", value)
}
}
}

Why does livedata return stale data from Room

I have come across a problem that I am not being able to solve without implementing fragile hacks.
I have a table Users.
And I am observing it via LiveData.
Everytime I launch an update on that table , my observer invokes twice. Once with the old value , and then with the newly updated one.
To illustrate the problem I have created a small example I would share below
UserDao.kt
#Dao
interface UserDao {
//region Inserts
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: User)
#Update
fun update(user:User)
#Query("select * from users ")
fun users(): LiveData<List<User>>
}
I observe the live data in my MainActivity.
observe(
database.usersDao().users()
){
Log.d("Here",it.name) // i first get the previous val then the new one
}
And this is how i am registering an update also in the MainActivity
GlobalScope.launch {
database.usersDao().update(
User(
102,
"John",
"asdas",
roleCsv = "aaa",
accessToken = AccessToken("asd", "asd", 0),
loggedIn = false
)
)
}
What transpires here is catastrophic for my system .
I get a user object that has a previous name , and then I get the updated "John"
the observe is just an extension method to easily register observers
fun <T : Any, L : LiveData<T>> LifecycleOwner.observe(liveData: L, body: (T) -> Unit) =
liveData.observe(this, Observer(body))
My question was is this by design ?. Can I do something such that only the final picture from the database invokes my observer?
I recommend observing the following liveData in your case:
Transformations.distinctUntilChanged(database.usersDao().users())
Source:
https://developer.android.com/reference/androidx/lifecycle/Transformations.html#distinctUntilChanged(androidx.lifecycle.LiveData%3CX%3E)
On the other note, hold the liveData reference inside androidx's viewModel.

Categories

Resources