I have been beating my head against the wall and I cannot understand why this is happening. I am working with the new Architectural Components for Android and I am having pr
You should not update the livedata reference once you set it and start observing it.Instead to update the live data with repository you should use the MediatorLiveData.
In your case do the following changes:
private MediatorLiveData<List<State>> states; // change
.....
.....
states.addSource(repo.getStatesFromCountry(countryID), newData -> states.setValue(newData)); //change
Well, I have reached a solution for this issue and found out how this LiveData things works.
Thanks to @MartinMarconcini for all his help is debugging ;)
So apparently, the observers are linked to the object you first set it up to. You cannot replace the object (by attribution) or otherwise it will not work. Also, if the value of your variable is going to change then you should use MutableLiveData
So the change necessary were:
1. Change from LiveData to MutableLiveData and pass that MutableLiveData to the repository when you need to update it
public class StatesViewModel extends ViewModel {
private MutableLiveData<List<State>> states; ;;CHANGED
private StatesRepository repo;
@Inject
public StatesViewModel(StatesRepository repository){
this.repo = repository;
}
public void init(String token){
states = repo.getStates(token);
}
public void getStatesFromCountry(String countryID){
repo.getStatesFromCountry(this.states, countryID); ;;CHANGED
}
public LiveData<List<State>> getStates(){
return this.states;
}
}
2. In the repository, update the MutableLiveData using setValue
@Singleton
public class StatesRepository {
private final WebServices services;
private final StateDao stateDao;
private final Executor executor;
@Inject
public StatesRepository(Executor executor, StateDao stateDao, WebServices services) {
this.services = services;
this.stateDao = stateDao;
this.executor = executor;
}
public MutableLiveData<List<State>> getStates(String token){
refreshStates(token);
final MutableLiveData<List<State>> data = new MutableLiveData<>();
data.setValue(stateDao.getAllStates());
return data;
}
;; CHANGED
public void getStatesFromCountry(MutableLiveData states, final String countryID){
states.setValue(stateDao.getStatesFromCountry(countryID));
}
private void refreshStates(final String token){
executor.execute(() -> {
if(stateDao.getNrStates() == 0){
try {
Response<List<State>> response = services.getStates("Bearer "+token).execute();
stateDao.insertAll(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
3. Changed the DAO to return List instead of LiveData>
@Dao
public interface StateDao {
@Query("SELECT * FROM states")
List<State> getAllStates();
@Query("SELECT * FROM states WHERE ctrId = :countryID")
List<State> getStatesFromCountry(String countryID);
@Query("SELECT COUNT(*) FROM states")
int getNrStates();
@Query("SELECT COUNT(*) FROM states WHERE ctrId = :countryID")
int getNrStatesByCountry(String countryID);
@Insert(onConflict = IGNORE)
void insertAll(List<State> states);
@Delete
void delete(State state);
}
4.Finally allow to perform queries in the main thread
AppModule.java
@Singleton @Provides
AppDatabase provideDb(Application app) {
return Room.databaseBuilder(app, AppDatabase.class,"unitail.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
Writing an answer for better discussion.
So I have (in Kotlin, sry) a model that is a list of notes (it’s just a sandbox app to play w/all this) and here’s my architecture: I don’t have a Repo, but I have Activity -> ViewModel -> Dao.
So Dao exposes a LiveData<MutableList<Note>>
@Query("SELECT * FROM notes")
fun loadAll(): LiveData<MutableList<Note>>
My ViewModel… exposes it through:
val notesList = database.notesDao().loadAll()
and my Activity (onCreate) does…
viewModel.notesList.observe(this,
Observer<MutableList<Note>> { notes ->
if (notes != null) {
progressBar?.hide()
adapter.setNotesList(notes)
}
})
This works. The adapter is a RecyclerView adapter that does literally nothing but:
fun setNotesList(newList: MutableList<Note>) {
if (notes.isEmpty()) {
notes = newList
notifyItemRangeInserted(0, newList.size)
} else {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return notes.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return notes[oldItemPosition].id == newList[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val (id, title, _, priority) = newList[newItemPosition]
val (id1, title1, _, priority1) = notes[oldItemPosition]
return id == id1
&& priority == priority1
&& title == title1
}
})
notes = newList
result.dispatchUpdatesTo(this)
}
}
If ANY other part of the app modifies that list of notes, the adapter updates automagically. I hope this gives you a playground to try a simple(r?) approach.
Dao must be same across all operations. You use different Dao instance for insert and observe