Room Count Query gives no result when ran on background thread - android

I have an Android Application which uses Room. I need to check what are the counts of item inserted in my table so I have written following code in my Dao interface
#Query("SELECT COUNT(*) FROM notes")
int getCount();
In my repository class I have written the code something like below
public int deleteNote(final NoteEntity note) {
final int[] count1 = new int[1];
executor.execute(new Runnable() {
#Override
public void run() {
count1[0] = mDb.schoolDao().getCount();
}
});
return count1[0];
}
Now when I call the above method in my Activity, I get the count as zero but when I force my application to run on the main thread i.e. removing the Runnable code, then it returns the valid count. I know firing any database query on the main thread is not a good option but I fail to understand why I get the count as zero when it runs on the background thread.
Just to be clear there is always data in the database.

You are getting 0 count because count1[0] returned earlier then actual DB query finished. Instead of using array use MutableLiveData. Something like this:
#Query("SELECT COUNT(*) FROM notes") MutableLiveData< Integer> getCount();
Now you'll get MutableLiveData instance directly after code invocation, but you won't be able to use it right now because it's empty. What you need to do is subscribe on new data:
mDb.schoolDao().getCount(). observe (this, new Observer<Integer>() { #Override public void onChanged(#Nullable final Integer val {count[0]=val; }};
Note that you don't need to run this code in background. Just subscribe right after count [] initialisation and when DB return a count value onChanged() method would invoke.
Not sure that it's mistake but you're returning count [0] in method that returns void. Are you sure that this is what you want?

Related

Why can't I assign the result of a query to a variable when using Room?

I am trying to put the result of a query into a variable to be used later on in my activity. I can put the results into a Log message but the variable is always null.
I ended up putting the result into a Textview but I would like to know why I can't just use the result?
Activity
Integer integer1;
mClothingViewModel.getTotalIds().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
integer1 = integer;
}
});
Model
LiveData<Integer> getTotalIds() { return mTotalIds;}
Repo
int getGetIntTotalClothes () {
return mGetIntTotalClothes;
}
Dao
#Query("SELECT COUNT(id) FROM Clothes ORDER BY COUNT(id)")
LiveData<Integer> getTotalNumClothes();
I researched and tried to use the Asynctask however I kept getting the "Cannot run on main thread" error.
Any explanation would be GREATLY appreciated!

Android: Able to return ArrayList but NOT a LiveData array list

I am unable to get a LiveData ArrayList from a Room database but I am able to retrieve a standard ArrayList and cannot figure out why.
I have run this code in debug mode and the ArrayList returns a size of 4, which it should. The LiveData ArrayList, when get value is used returns null. I have run the LiveData query both within an executor and outside of the executor and it returns null.
Declarations
public LiveData<List<CourseEntity>> courseEntities;
private List<CourseEntity> courseData = new ArrayList<>();
Code outside of executor
public void loadData(final int termId) {
courseEntities = courseRepository.getCourseByTermId(termId);
courseData = courseEntities.getValue();
}
Code inside executor
public void loadData(final int termId) {
executor.execute(new Runnable() {
#Override
public void run() {
courseEntities = courseRepository.getCourseByTermId(termId);
courseData = courseEntities.getValue();
}
});
}
Code using just an ArrayList
public void loadData(final int termId) {
executor.execute(new Runnable() {
#Override
public void run() {
courseData = courseRepository.getCourseByTerm(termId);
}
});
}
Queries from Dao
#Query("SELECT * FROM course " +
"WHERE term_id = :termIdSelected ORDER BY course_start" )
LiveData<List<CourseEntity>> getCourseByTermId(int termIdSelected);
#Query("SELECT * FROM course WHERE term_id = :termIdSelected ORDER BY course_start")
List<CourseEntity> getCourseByTerm(int termIdSelected);
This produces a null value for the LiveData instead of a value of 4 like the plain ArrayList produces. The only difference being the LiveData wrapper for the result. Any wisdom someone can share would be most appreciated.
When you have a Room #Dao return a LiveData (or an RxJava type like Observable or Single), the generated implementation will do the actual work on a background thread. So, when getCourseByTermId() returns, the work will not yet have begun, so the LiveData will not have results yet.
Reactive types, like LiveData, are meant to be observed. So, your activity/fragment/whatever would observe() the LiveData and react to the result when it is delivered.

Notify (RX) data observer only when table size changed

I am using reactive observers (RxJava2) for listening DB changes.
Here is subscription method:
fun subscribeGetTasks(): Flowable<List<Task>> {
val query = taskBox.query().build()
return RxQuery.observable(query).toFlowable(BackpressureStrategy.BUFFER)
}
However, I would like be only notified when table size changed.
How to achieve that?
Thank you
There is no special feature for that. Your observer would have to call count() on the box if you have no query conditions (otherwise there would also be a count() on the Query object).
However, better not use a Query if you are only interested in the count for all objects in a box. Instead, you can do this with ObjectBox:
DataObserver<Class<Task>> taskObserver = new DataObserver<Class<Task>>() {
#Override public void onData(Class<Note> data) {
long taskCount = taskBox.count();
}
};
boxStore.subscribe(Task.class).observer(taskObserver);
For additional reading, please check the docs.

How to get the row count of Room database in android?

I'm following the practice of having a Repository and a Dao and so on. I was trying to get the row count in my database repository by having a function
int getNumFiles() {
List<AFile> lst = files.getValue(); // files is of type LiveData<List<AFile>> files;
if (lst == null) {
return 0;
} else {
return lst.size();
}
}
But lst always evaluates to null. I guess it has something to do with me not being allowed to query the DB from the UI thread or something? Should I implement it like one implements adding or deleting an element? In other words have a function in the Dao which is called via an AsyncTask in the Database repository? I'm confused about how to do this very simple thing.
There is this answer which shows what one would write in the Dao to find out the number of rows, but it does not explain how the repository should call this.
Room database Count Table Row
#Query("SELECT COUNT(column_name) FROM tableName")
LiveData<Integer> getRowCount(); //with LiveData
#Query("SELECT COUNT(column_name) FROM tableName")
int getRowCount();
I ended up doing it like this (using a new thread for the query).
In the Dao
#Query("SELECT COUNT(id) FROM table")
int getCount();
In the repository
int getNumFiles() {
return afileDao.getCount();
}
Where I need it
final AtomicInteger fcount = new AtomicInteger();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
int num = f_repo.getNumFiles();
fcount.set(num);
}
});
t.setPriority(10);
t.start();
t.join();
// use as fcount.get()
Let's see if this works. I may be off base, but I have struggled with this same issue trying to learn Room databases and most recently trying to get the row count of the table I was working with.
(This is my first post, so I apologize for the shortness of it and welcome constructive thought to make it better.)
Starting with the Dao, I declared the method with the #Query() annotation. This is the point where we will define the query we will be using to retrieve the desired information.
#Query("SELECT COUNT(*) FROM word_table")
LiveData<Integer> getCount();
Second, carry this through the Repository. The Repository will be calling our Dao class to retrieve information and essentially pass the query.
public LiveData<Integer> getCount() {
return mWordDao.getCount();
}
Third, bring it into the ViewModel. The ViewModel will be called by the (in this case) MainActivity and in turn will call the getCount() method from the Repository and back down the chain.
// count
public LiveData<Integer> getCount() { return mRepository.getCount(); }
Finally, create the observable in the MainActivity, seeing as I encased the value with a LiveData<> wrapper.
mWordViewModel.getCount().observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer integer) {
word_count.setText(String.valueOf(integer));
}
});
I know that this is simplistic, short and leaves out a lot of detail, but after going over the Room Database code a large number of times, this worked for me to be able to display the number of rows in the database table I was referencing. And it seems to be the way that the Room databases are intended to work.
(The code I was using as a base for branching out into retrieving the row count was grabbed from the codebase labs provided by Google for Room Databases part I.)
You can reach them with the following link and click on the one for Room Databases - Part 1:
Codelabs for Android Developers
Scott
I didn't need LiveData and I used a Coroutine:
// DAO
#Query("SELECT COUNT(*) FROM some_table")
suspend fun getCount(): Int
// REPOSITORY
fun getCount(): Int = runBlocking {
val count = async {
dao.getCount()
}
count.start()
count.await()
}
// VIEWMODEL
when (val count = repository.getCount()) {
// do stuff with count
}
I think a nicer way to do miniature things in the background thread is to create a Handler & HandlerThread and use them to perform one liner tasks.
//The handlers to perform tasks on the background threads
override lateinit var mHandler: Handler
override lateinit var mHandlerThread: HandlerThread
override fun start() {
//Instantiate the handlerThread
mHandlerThread = HandlerThread(MainPresenter::class.java.simpleName)
//A call to the start method has to be executed manually
mHandlerThread.start()
mHandler = Handler(mHandlerThread.looper)
}
And wherever you want to call something in the background thread, simply :
mHandler.post { getTableCountInBg() }
I was in the midst of typing what #Sameer Donga linked to, but refer that instead. Call it like above.
P.S. Ignore the override annotations. They're there because I enforce it on a presenter.
#Query("SELECT COUNT(column_name) FROM table)
LiveData getTotalNumberOfColumns();
or do this if you don't want multiple occurences of a value in the column
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData getTotalNumberOfColumns();
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData<Integer> getTotalNumberOfRows();
Add DISTINCT as an argument to the COUNT function.

LiveData List doesn't update when updating database

I'm currently refactoring legacy code to use Android Architecture Components and set up a room db and volley requests within a kind of repository pattern.
So the presentation/domain layer asks the repository to get LiveData-Objects to observe or tell him to synchronize with the server, after which old db entries are deleted and all current ones refetched from the server.
I've written tests for the synchronization part, so I'm sure, that the objects get fetched and inserted to the database correctly. But when writing a test to observe the entries of that db table (and test if the objects were saved correctly with everything there needs to be done before putting them into db) the LiveData> I'm observing, doesn't get triggered.
In the following snippet you can assume, that the synchronizeFormsWithServer(...) method does work correctly and is performing database operations asynchronously. It contains operations which deletes all Form-Objects from the db which are not present in the list of Forms fetched from the server and inserts all new ones. Since at the start of the test the database is empty this shouldn't matter that much
The test in which the observer doesn't get triggered:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
formRepository.allForms().observe(owner, formList ->
{
if (formList != null && formList.isEmpty())
{
for (Form form : formList)
{
testForm(form);
l.countDown();
}
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
The FormRepository code:
#Override
public LiveData<List<Form>> allForms()
{
return formDatastore.getAllForms();
}
The datastore:
#Override
public LiveData<List<Form>> getAllForms()
{
return database.formDao().getAllForms();
}
The formDao code (database is implemented how you'd expect it from room):
#Query("SELECT * FROM form")
LiveData<List<Form>> getAllForms();
It may very well be, that I didn't understand something about the LiveData-Components, because this is my first time using them, so maybe I got something fundamentally wrong.
Every bit of help is very much appreciated :)
PS: I stumbled across THIS post, which discusses a similar issue, but since I'm currently not using DI at all and just use a single instance of the formrepository (which has only one instance of formDao associated) I don't think it's the same problem.
Ok, so I found the solution, although I don't know, why it behaves that way.
Remember when I said "don't worry about the synchronize method"? Well... turns out there were a couple of things wrong with it, which delayed the solution further.
I think the most important error there was the method to update the objects in the database when the network response came in.
I used to call
#Update
void update(Form form)
in the dao, which for unknown reasons doesn't trigger the LiveData-Observer. So I changed it to
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Form form);
After doing this I could get the Form-LiveData from my repository as easy as
LiveData<List<Form>> liveData = formRepository.allForms();
Then subscribe to it as usual.
The previously failed test looks like this now:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
final SortedList<Form> sortedForms = new SortedList<Form>(Form.class, new SortedList.Callback<Form>()
{
#Override
public int compare(Form o1, Form o2)
{
return o1.getUniqueId().compareTo(o2.getUniqueId());
}
#Override
public void onChanged(int position, int count)
{
Log.d(LOG_TAG, "onChanged: Form at position " + position + " has changed. Count is " + count);
for (int i = 0; i < count; i++)
{
l.countDown();
}
}
#Override
public boolean areContentsTheSame(Form oldItem, Form newItem)
{
return (oldItem.getContent() != null && newItem.getContent() != null && oldItem.getContent().equals(newItem.getContent())) || oldItem.getContent() == null && newItem.getContent() == null;
}
#Override
public boolean areItemsTheSame(Form item1, Form item2)
{
return item1.getUniqueId().equals(item2.getUniqueId());
}
#Override
public void onInserted(int position, int count)
{
}
#Override
public void onRemoved(int position, int count)
{
}
#Override
public void onMoved(int fromPosition, int toPosition)
{
}
});
LiveData<List<Form>> ld = formRepository.allForms();
ld.observe(owner, formList ->
{
if (formList != null && !formList.isEmpty())
{
Log.d(LOG_TAG, "shouldSaveFormsFromServerIntoDb: List contains " + sortedForms.size() + " Forms");
sortedForms.addAll(formList);
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
I know that exactly 19 Forms will get fetched from the server and then every Form will get changed once (first time I load a list containing all Forms with reduced data, and the second time I load every item from the server again replacing the old value in the db with the new value with more data).
I don't know if this will help you #joao86 but maybe you have a similar issue. If so, please make sure to comment here :)
You have to use the same database instance at all places.
=> Use a singleton for that
I had a similar issue with yours --> LiveData is not updating its value after first call
Instead of using LiveData use MutableLiveData and pass the MutableLiveData<List<Form>> object to the Repository and do setValue or postValue of the new content of the list.
From my experience with this, which is not much, apparently the observer is connected to object you first assign it too, and every change must be done to that object.

Categories

Resources