Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 453
Antonio Dell Avatar asked Aug 31 '17 07:08

Antonio Dell


3 Answers

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

like image 107
Antonio Dell Avatar answered Oct 14 '22 19:10

Antonio Dell


You have to use the same database instance at all places.

=> Use a singleton for that

like image 7
Tobias Avatar answered Oct 14 '22 18:10

Tobias


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.

like image 3
joao86 Avatar answered Oct 14 '22 18:10

joao86