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?
Let's first look at the definition mentioned in the official Android documentation: LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.
Else, we can use callback or RxJava in ViewModel. Another compromise is implement MediatorLiveData (or Transformations) and observe (put your logic here) in ViewModel. Notice MediatorLiveData observer won't trigger (same as Transformations) unless it's observed in Activity/Fragment.
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.
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With