Room - LiveData observer does not trigger when database is updated

12,610

Solution 1

I had a similar problem using Dagger 2 that was caused by having different instances of the Dao, one for updating/inserting data, and a different instance providing the LiveData for observing. Once I configured Dagger to manage a singleton instance of the Dao, then I could insert data in the background (in my case in a Service) while observing LiveData in my Activity - and the onChange() callback would be called.

It comes down to the instance of the Dao must be the same instance that is inserting/updating data and providing LiveData for observation.

Solution 2

In my case, it was because I was using a MediatorLiveData to convert the entities returned from the database and forgot to call setValue() with the converted result, so the mediator was only relying requests to the database but never notifying results.

override fun getItems() = MediatorLiveData<List<Item>>().apply {
    addSource(itemDao().getItems()) {
        // I was previously only converting the items, without calling 'value ='
        value = it.map(ItemWithTags::toDto)
    }
}
Share:
12,610
Bitcoin Cash - ADA enthusiast
Author by

Bitcoin Cash - ADA enthusiast

Bitcoin Cash donations: bitcoincash:qpzkuqj5j0f4f6h5q7j2qnzuwlj0prat8c8xek3ndn ADA donations: addr1q94a6fs54ktss7k8yezsd9y3w6f7jlz2wa9zjxe7zlh869636m56ftcgfwp5ernjfu422dh3869hhnksyvn9dsla4gjqzl92xe

Updated on June 06, 2022

Comments

  • Bitcoin Cash - ADA enthusiast
    Bitcoin Cash - ADA enthusiast about 2 years

    I am trying to find out in the code below, why is it that Room's LiveData observable does not give me new shifts once I populate the database with new data.

    This is put on my activity's onCreate method:

    shiftsViewModel = ViewModelProviders.of(this).get(ShiftsViewModel.class);
    shiftsViewModel
                .getShifts()
                .observe(this, this::populateAdapter);
    

    This is the populateAdapter method:

    private void populateAdapter(@NonNull final List<Shift> shifts){
    
        recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(shifts));
    }
    

    I also have the following code that populates the database (I use RxJava to do the work on an IO thread since Room needs its code to be called outside the main thread):

    @Override
    public Observable<List<Shift>> persistShifts(@NonNull final List<Shift> shifts){
    
        return Observable.fromCallable(() -> {
    
            appDatabase.getShiftDao().insertAll(shifts);
            return shifts;
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
    }
    

    The problem I have occurs when I call persistShifts after I start observing my shiftsViewModel. I would expect that my observer (LiveData) would be triggered with all the newly added shifts. It turns out the observer is triggered, but an empty list of shifts is returned instead. The only way to make it "work" is if I leave the activity (therefore destroying the current ViewModel) and enter again. This time the viewModel's LiveData gives me all the shifts previously persisted, as expected.

    Here is the rest of the code:

    @Entity
    public class Shift{
    
       @PrimaryKey
       private long id;
    
       private String start;
       private String end;
       private String startLatitude;
       private String startLongitude;
       private String endLatitude;
       private String endLongitude;
       private String image;
       ...
    

    DAO:

    @Dao
    public interface ShiftDAO {
    
       @Query("SELECT * FROM shift")
       LiveData<List<Shift>> getAll();
    
       @Query("SELECT * FROM shift WHERE id = :id")
       LiveData<Shift> getShiftById(long id);
    
       @Insert(onConflict = OnConflictStrategy.REPLACE)
       void insertAll(List<Shift> shifts);
    }
    

    ViewModel:

    public class ShiftsViewModel extends AndroidViewModel{
    
       private final ISQLDatabase sqlDatabase;
    
       private MutableLiveData<Shift> currentShift;
       private LiveData<List<Shift>> shifts;
       private boolean firstTimeCreated;
    
    
       public ShiftsViewModel(final Application application){
    
          super(application);
    
          this.sqlDatabase = ((ThisApplication) application).getSQLDatabase();
          this.firstTimeCreated = true;
       }
    
       public MutableLiveData<Shift> getCurrentlySelectedShift(){
    
          if(currentShift == null){
             currentShift = new MutableLiveData<>();
          }
    
          return currentShift;
       }
    
       public LiveData<List<Shift>> getShifts() {
    
          if(shifts == null){
             shifts = sqlDatabase.queryAllShifts();
          }
    
         return shifts;
       }
    
       public void setCurrentlySelectedShift(final Shift shift){
    
          currentShift = getCurrentlySelectedShift();
    
          currentShift.setValue(shift);
       }
    
       public boolean isFirstTimeCreated(){
          return firstTimeCreated;
       }
    
       public void alreadyUsed(){
          firstTimeCreated = false;
       }
    }
    

    Why am I not getting the list of shifts I persist in the observe() callback straightaway?