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.
Related
So currently I have a Dao with a function that emits a Flow<>
#Query("SELECT * FROM ${Constants.Redacted}")
fun loadAllContacts(): Flow<List<Redacted>>
I am calling this from a repository like so
val loadAllContacts: Flow<List<Redacted>> = contactDao.loadAllContacts()
I am injecting the repository into the viewModel's constructor, and then at the top of my viewModel I have a val like so
val contacts: LiveData<List<Redacted>> = contactRepository.loadAllContacts.asLiveData()
Which is being observed in my Activity like so
viewModel.contacts.observe(this) { contacts ->
viewModel.onContactsChange(contacts)
}
My thinking is that the Flow is converted to a LiveData, and then I can observe this LiveData from my activity and kick off this function to actually update the viewModel upon the data being updated.
For now onContactsChange just looks like
fun onContactsChange(list: List<Redacted>) {
Timber.i("VIEW UPDATE")
}
The problem is that I only see this Timber log upon opening the activity, and never again. I verified that data IS going into my database, and I verified that an insert occurred successfully while the activity & viewModel are open. But I never see the log from onContactsChange again. When I close the activity, and reopen it, I do see my new data, so that is another reason I know my insert is working correctly.
I would like to add that I am using a single instance (singleton) of my repository, and I think I can verify this by the fact that I can see my data at all, at least when the view is first made.
Figured it out:
Note: If your app runs in a single process, you should follow the singleton design pattern when instantiating an AppDatabase object. Each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances within a single process.
If your app runs in multiple processes, include enableMultiInstanceInvalidation() in your database builder invocation. That way, when you have an instance of AppDatabase in each process, you can invalidate the shared database file in one process, and this invalidation automatically propagates to the instances of AppDatabase within other processes.
It's a little bit hard to follow your question, but I think I see the overall problem with your Flow object not updating the way you want it too.
Following this quick tutorial, it seems that first you should declare your Flow object inside your Repository the same way you're already doing
val loadAllContacts: Flow<List<Redacted>> = contactDao.loadAllContacts()
and have your VM 'subscribe' to it by using the collect coroutine which would then allow you to dump all this data into a MutableLiveData State
data class YourState(..)
val state = MutableLiveData<YourState>()
init {
contactRepository.loadAllContacts().collect {
if (it.isNotEmpty()) {
state.postValue(YourState(
...
)
}
}
}
that your Activity/Fragment could then observe for changes
viewModel.state.observe(.. { state ->
// DO SOMETHING
})
P.S. The tutorial also mentions that because of how Dao's work, you might be getting updates for even the slightest of changes, but that you can use the distinctUntilChanged() Flow extension function to get more specific results.
in my Dao I've defined a Query like this to check whether the database is empty or not:
#Query("SELECT * from meal_table LIMIT 1")
suspend fun getAnyMeal(): LiveData<Array<Meal>>
Within my populateDatabse function I would like to check, whether any item is inside my database with something like this:
suspend fun populateDatabase(mealDao: MealDao) {
if ((mealDao.getAnyMeal()).size < 1)
...
}
Unforunately size doesnt work in this context unless I am doing something wrong.
If someone has a tipp on how to solve this I would apreciate it! Thank you!
Unforunately size doesnt work in this context
It is because getAnyMeal returns you LiveData which has no property named size. LiveData is an observable object. It means that LiveData object you get by calling this method will return to its observers (only the ones who "subscribed" to updates) an array of Meal objects when this array will be available.
First of all, when you are using LiveData with Room you sort of giving Room a signal that you are not requesting a response immediately. LiveData is used when you want to get updates in future that will happen on change of any object in DB. Also you may want to use LiveData when you want to execute the SELECT query asynchronously. It means you call getAnyMeal method and it does not block UI thread.
When you are using suspend keyword you can remove LiveData from return type. When suspend function is executed it will synchronously return you the result.
Solution
Update getAnyMeal() to the next form or create the new method as it is declared below:
#Query("SELECT * from meal_table LIMIT 1")
suspend fun getAnyMeal(): Array<Meal>
If you declare getAnyMeal method like this you will be able to call size property on the return type as it directly returns you an array.
On LiveData with Room:
Room doesn't support database access on the main thread unless you've called allowMainThreadQueries() on the builder because it might lock the UI for a long period of time. Asynchronous queries—queries that return instances of LiveData or Flowable—are exempt from this rule because they asynchronously run the query on a background thread when needed.
Observable queries with LiveData for more info.
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.
I have two activities. First activity shows list of notes. Notes themselves are lists.
I use Android Architecture Components: ViewModel, LiveData; with Repository, Room, Dao, etc.
So, I make a method getAllNotes() in Dao, Repository and ViewModel like in google sample apps. In onCreate method of first activity I call observe and set adapter's content of a RecyclerView. And it works fine - it shows the list with Note titles.
Like that:
override fun onCreate(savedInstanceState: Bundle?) {
//some code
viewModel = obtainViewModel()
viewModel.getAllNotes().observe(this, Observer<List<Notes>> { notes ->
recView.setNote(notes)
}
}
Then I have a button that starts new Activity to create new Note. That note contains list of Lines which for now contains only string and foreign key.
data class Line {
var id: Long? = null
var note_id: Long? = null
var payload: String? = null
}
Note and Line are one-to-many relation and they are connected by id of Note and foreign key note_id in Line.
(I don't write here all of the code, it works, trust me)
The problem is, that to insert Lines in database I firstly need to insert the parent Note and I do that. And it works almost OK too. But the liveData of the getAllNotes() from the first Activity gets notified by this insertion. And if the user, as a result, decides to delete all the lines and go back to the first activity even if I delete temporary Note entity from the database the list on the first Activity shows it for a moment because it gets deleted in a background with a small delay.
What I see as a solution:
1) Unsubscribe observers from livedata. I tried to do it in onStop method, but it gets called after the onCreate method of the second activity where the entity is being created, so the livedata already gets notified and observers are removed after temporary Note passed into the list.
2) Not use Room/SQLite as cache. Since this Note and Lines are not guaranteed to stay then and shouldn't be shown or inserted into a table. So, I can keep it all in properties of viewModel (i.e. in memory). But I see a lot of overhead work to save these entities through screen rotation, minimizing the app and all that stuff with saving state and restoring it.
3) Create two additional entities like CachedNote and CachedLine and corresponding tables, to work with it until I decide to persist the work, insert it into original tables and show it.
4) Add property to the Note entity like "visible" and add this parameter to Query, to make entity note shown, until I decide to persist the work. But there could be a lot of "updateNoteWithLines" every where.
What should I do? I didn't google anything useful.
I know it's like "What's the best way question", forgive me.
You can try to call the observe in onResume and then call removeObserver in onPause, that way the Activity will not be updated, please look at the example here.
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