Room LiveData not updating correctly - android

I have a room query that selects the orders submitted within the last hour. when I add/edit the orders everything works great.
#Query("SELECT * FROM OrderTable WHERE datetime(delivery_date) > datetime('now','-1 hour','localtime') ORDER BY datetime(delivery_date) DESC")
fun getAllAvailable(): LiveData<List<Order>>
The problem is when I have some orders within the last hours and there is no edit/add in the orders table, orders don't disappear after their time passed the one-hour range!
Let me explain with a simple example:
There is an order with delivery_date of 2021-05-22 14:00, when the time of device is 2021-05-22 14:30 the livedata result show the order and it is okay, when time pass and it is 2021-05-22 15:01 it should remove the order from the result but it doesn't and always show the order until next add/edit occur to the order table and live data updates!!
P.S: I should mention that Room database instance is singleton using hilt.

You are using query to fetch LiveData for last hour but it will not be executed unless there is a change in room database or your activity/fragment's lifecycle is resumed.
Refer this again : Documentation for LiveData
Here is simple sequence:
Database changes
Livedata updates the observers
Observers are executed
In your case, you are implementing the logic in your query, it does not know that it should be executed.
You can use a runnable to achieve what you want. Consider this.

Related

Android architecture LiveData and Repositories

I am converting my application to room database and try to follow the google architecture best practices based on "Room with a View".
I am having trouble to understand the repository in terms of clean architecture.
The Words database example contains only one table and one view using it, making it a simple HelloWorld example. But lets start with that.
There is a view which displays a list of words. Thus all words need to be read from the database and displayed.
So we have a MainActivity and a Database to connect.
Entity Word
WordDao to access DB
WordViewModel: To separate the activity lifecycle from the data lifecycle a ViewModel is used.
WordRepository: Since the data maybe kept in a database or the cloud or whatever the repository is introduced to handle decision, where data comes from.
Activity with the View
It would be nice if the view is updated when the data changes, so LiveData is used.
This in turn means, the repository is providing the LiveData for the full table:
// LiveData gives us updated words when they change.
val allWords: LiveData<List<Word>>
This is all fine for a single view.
Now to my questions on expanding this concept.
Let us assume, the word table has two columns "word" and "last_updated" as time string.
For easier comparison the time string needs to be converted to milliseconds, so I have a function.
Question: Where to put the fun queryMaxServerDateMS() to get the max(last_updated)?
/**
* #return Highest server date in table in milliseconds or 1 on empty/error.
*/
fun queryMaxServerDateMS(): Long {
val maxDateTime = wordDao.queryMaxServerDate()
var timeMS: Long = 0
if (maxDateTime != null) {
timeMS = parseDateToMillisOrZero_UTC(maxDateTime)
}
return if (timeMS <= 0) 1 else timeMS
}
For me it would be natural to put this into the WordRepository.
Second requirement: Background job to update the word list in the database.
Suppose I now want a Background Job scheduled on a regular basis which checks the server, if new entries were made and downloads them to the database. The app may not be open.
This question just relays to the question of the above queryMaxServerDateMS.
The job will basically check first, if a new entry was made by asking the server if an entry exists which is newer then the max known entry.
So I would need to get a new class WordRepository, do my query, get max last_update and ask the server.
BUT: I do not need the LiveData in the background job and when val repositoy = WordRepository the full table is read, which is needless and time-, memory and batteryconsuming.
I also can think of a number of different fragments that would require some data of the word table, but never the full data, think of a product detail screen which lists one product.
So I can move it out to another Repository or DbHelper however you want to call it.
But in the end I wonder, if I use LiveData, which requires the View, ViewModel and Repository to be closely coupled together:
Question: Do I need a repository for every activity/fragment instead of having a repository for every table which would be much more logical?
Yes, with your current architecture you should put it in the Repository.
No, you don't need a repository for every activity/fragment. Preferably, 1 repository should be created for 1 entity. You can have a UseCase for every ViewModel.
In Clean architecture there's a concept of UseCase / Interactor, that can contain business logic, and in Android it can act as an additional layer between ViewModel and Repository, you can create some UseCase class for your function queryMaxServerDateMS(), put it there and call it from any ViewModel you need.
Also you can get your LiveData value synchronously, by calling getValue().
You do not need repository for each activity or fragment. To answer your question about getting max server time - when you load words from db you pretty much have access to entire table. That means you can either do that computation yourself to decide which is the latest word that's added or you can delegate that work to room by adding another query in dao and access it in your repo. I'd prefer latter just for the simplicity of it.
To answer your question about using repo across different activities or fragment - room caches your computations so that they are available for use across different users of your repo (and eventually dao). This means if you have already computed the max server time in one activity and used it there, other lifecycle owners can use that computed result as far as the table has not been altered (there might be other conditions as well)
To summarize you're right about having repository for tables as opposed to activities or fragments

Retrieving a HashMap LiveData via Room Library

I have a social networking app which displays a list of users, and am looking to have an efficient way of being able to retrieve an object from my LiveData using its primary key.
Example: Retrieve a set of User POJOs from within my LiveData<List<User>> given a LIST of userId Integers (ie, users 12, 5, 7, and 1). I need to be able to look up these users by the userId for display in the appropriate order in the UI.
I believe I want something more like LiveData<Map<Integer, User>>, but how could I implement this using the Room database, without breaking the LiveData callbacks from my local DB -> Room -> LiveData -> UI?
PROPOSAL 1:
Change my Room implementation to somehow return a LiveData containing a HashMap of <userId,User>.
Current Room implementation:
#Query("SELECT * FROM users WHERE user_id in :userIds LIMIT 1")
LiveData<List<User>> getUsers(List<Integer> userIds);
Proposed Room implementation (no idea if something like this is possible or what it would even look like):
#Query("SELECT * FROM users WHERE user_id in :userIds LIMIT 1")
LiveData<**HashMap**<Integer,User>> getUsers(List<Integer> userIds);
PROPOSAL 2:
Have a list of many LiveData objects WITHIN a Map:
Map<Integer,LiveData<User>> liveDataUsers;
This might be something to look into, but I'm worried that having potentially hundreds/thousands of LiveData objects within a map is bad design and could also lead to performance issues / too many open LiveData internal callback threads.
PROPOSAL 3:
Something else??? I feel like I am missing something easy here. How are others looking up objects within their LiveData using only their primaryKey?
edit: this is something I'd like to achieve at the Repo / Model level and not at the activity level, as this LiveData will be re-used throughout the app.

Android - Room Persistence Library - Access data both synchronously & through observer based on need

Problem:
I am using Room Persistence Library and so far everything is working fine except that there is a data from select query which I need synchronously as I am calling it from a Periodic Job (Work Manager's Worker). I have defined the return type to be LiveData as I am also accessing it for display purposes in UI and so observers are great for that but now I also need the same data in Job.
Code Snippet
#Query("SELECT * from readings ORDER BY date, time ASC")
LiveData<List<Reading>> getAllReadings();
Tried
I have tried the getValue() method in LiveData but it returns null as the data is not loaded in LiveData while making the query.
readingDao().getAllReadings().getValue() // returns null
Possible Solution
There is only one solution that I can think of which is to duplicate the getAllReadings query with a different name and return type (without LiveData) but I don't think this is a clean approach as it increases duplication of code just to get a synchronous return type.
Please let me know if there is any other solution or perhaps some way to synchronously access data from LiveData variable.
You can allow main thread query when you initialize Room DB, but it's clearly not desirable. This will give you the synchronous behavior but will block user interface. Is there a specific reason you want this to be synchronous?
The reason why getValue() is returning null is because Room is querying data asynchronously. You can attach an observer or a callback function to get result when the query is finished. You can display the result to the UI or chain another call for sequential operation etc from there.
I use RxJava to wrap my query request for asynchronous query but I you can also use AsyncTask.

What triggers LiveData onChanged()?

I'm using Room and in the Dao I have this method:
LiveData<List<Books>> getAllBooks();
In MainActivity I have subscribed to that method from the ViewModel. Changes to the data trigger the onChanged() callback:
viewModel.getAllBooks()
.observe(this, books -> {
Log.d(TAG, "onChanged()");
booksListAdapter.setData(new ArrayList<>(books));
});
What I would like to know is what constitutes an update? When the app first starts I do 100 insertions, each of them changes the database but the onChanged() is not invoked 100 times. Last time I checked it called onChanged() the first time which I think it always calls when starting and then two more calls.
Can I have control over this? For example if I know I will be doing 100 insertions perhaps it would be better if I only got the callback at the end of the insertions.
You don't have control of that. What you can do is use MediatorLiveData and post the value after all insertions. Whenever you update, delete or insert Room knows that there has been change but doesn't know what has been changed. So it just re-queries and sends the results to observing LiveData
Check this blog and mainly section 7. Avoid false positive notifications for observable queries. Author gives pretty good example of MediatorLiveData which is similar to what you are looking for

Checking if RoomDatabase is empty while using LiveData

I am trying to use RoomDatabase in my Android App. And I am using LiveData to be able to refresh my changes automatically inside my fragment.
The first time I am running my app I am getting the data from the API, creating my RoomDatabase and storing my data.
The second time I run my app I want to check if my DataBase is not empty. But while using LiveData: the following code is returning null.
AppDatabase.getInstance(getContext()).getRecipeDao().getAllRecipes().getValue();
I have read that "if the response is an observable data type, such as Flowable or LiveData, Room watches all tables referenced in the query for invalidation".
How to check if my RoomDatabase has data or is empty?
So after implementing myself I found that you need to do a few things:
Make sure you have an Observer for changes to the LiveData
You need to call observeForever(Observer<T> observer) unless you are using a LiveCyclerOwner then use that instead with: observe (LifecycleOwner owner, Observer<T> observer)
Finally, there is an interesting note on getValue():
Returns the current value. Note that calling this method on a
background thread does not guarantee that the latest value set will be
received
So to reiterate, I think your approach does not work.
You will need to create some type of separate check rather than use a method that returns a LiveData class as noted since it does not guarantee the latest value set is received by calling getValue().
I would recommend something super simple in the end such as adding a new method to your Dao
#Query("SELECT * FROM recipes LIMIT 1")
Recipe getAnyRecipe();
and do this check looking for null to see if anything exists in the recipes table.

Categories

Resources