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?

2 Answers2

70

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.

mukwux
  • 1,276
  • 11
  • 12
  • Yes, to get the live updates you have to make sure that the instance of `Dao` must be same across all operations. – Deepak Goyal Feb 14 '18 at 17:07
  • 1
    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 `Dao`s 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. – devrocca Feb 23 '18 at 21:30
  • 2
    Had similar issue. Resolved by holding the database as singleton (not the DAOs) – user3118604 Mar 13 '18 at 15:43
  • 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. – Bill Aug 14 '19 at 17:48
  • 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. – Pratik Mhatre Aug 19 '19 at 09:38
  • 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. :/ – Luis Nov 30 '19 at 15:15
  • thanks, i've been looking through this for an hour – Fahad Alotaibi May 27 '21 at 17:25
  • Even after declaring `Dao` as singleton had to share `ViewModel` between fragments using `activityViewModels` instead of `viewModels`. Not sure why – Sergei Kozelko Jul 26 '22 at 06:38
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)
    }
}
guy.gc
  • 3,359
  • 2
  • 24
  • 39