Room - LiveData observer does not trigger when database is updated
P

2

31

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?

Painting answered 25/6, 2017 at 1:41 Comment(0)
F
72

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.

Footsore answered 6/7, 2017 at 20:40 Comment(9)
Yes, to get the live updates you have to make sure that the instance of Dao must be same across all operations.Brick
I incurred in the same problem and thanks to your anwser I found out I had forgot @Singleton on my RoomDatabase provision method. I'd like to add that in my case, I have 2 different Daos querying the same table, of which one is related to a classic entity and one to a wrapper class with 2 embedded entities, to reproduce a join query. Although the objects are different, the LiveData gets updated correctly, so it may be that it's the RoomDatabase who has to be singleton.Dirkdirks
Had similar issue. Resolved by holding the database as singleton (not the DAOs)Lesslie
Same issue but mine was due to fact that I was doing inserts/updates in a separate process/service in the app. Once I moved back to main app process it started working.Clermontferrand
Exactly, if you are using the Dagger to have an instance of your Db or any DataManager, you must annotate your db instance providing method in your AppProvider as @Singleton, this solved the issuefor me.Bluey
I'm having the exactly same problem but couldn't solve yet. Still learning about Dagger and even by marking my classes as singleton, everytime I start my IntentService a new instance is created. :/Quean
thanks, i've been looking through this for an hourBryner
Even after declaring Dao as singleton had to share ViewModel between fragments using activityViewModels instead of viewModels. Not sure whyRockandroll
Adding @Singleton to the dao provider method solved the problem!Laurent
M
3

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)
    }
}
Measured answered 11/4, 2020 at 11:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.