LiveData is not updating its value after first call
Solution 1
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();
}
Solution 2
Dao must be same across all operations. You use different Dao instance for insert and observe
Solution 3
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
Solution 4
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.
Related videos on Youtube
joao86
I am person who likes his job, that likes to have problems to solve and having to find solutions for it. I am experienced in communicating with demanding clients and I am very easily adaptable to different environments and situations. Over the last few years, I have been developing my knowledge regarding mobile programming, specially Android.
Updated on July 13, 2022Comments
-
joao86 almost 2 years
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 problems updating a LiveData with a List of Objects. I have two spinners. When i change the option in the first one, The second one must have its content changed. But this last part is not happening. Can anyone help me?
State.java
@Entity(tableName = "states") public class State{ @PrimaryKey(autoGenerate = false) private int id; private String name; @ColumnInfo(name = "countryId") private String CountryId; @Ignore private Object geoCenter, geoLimit; public State(){ } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCountryId() { return CountryId; } public void setCountryId(String countryId) { CountryId = countryId; } }
StateDAO
@Dao public interface StateDao { @Query("SELECT * FROM states") LiveData<List<State>> getAllStates(); @Query("SELECT * FROM states WHERE countryId = :countryID") LiveData<List<State>> getStatesFromCountry(String countryID); @Query("SELECT COUNT(*) FROM states") int getNrStates(); @Query("SELECT COUNT(*) FROM states WHERE countryId = :countryID") int getNrStatesByCountry(String countryID); @Insert(onConflict = IGNORE) void insertAll(List<State> states); @Delete void delete(State state); }
StateRepository
@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 LiveData<List<State>> getStates(String token){ refreshStates(token); return stateDao.getAllStates(); } public LiveData<List<State>> getStatesFromCountry(String countryID){ return stateDao.getStatesFromCountry(countryID); } private void refreshStates(final String token){ executor.execute(() -> { Log.d("oooooo", stateDao.getNrStates() + ""); if(stateDao.getNrStates() == 0){ try { Response<List<State>> response = services.getStates("Bearer "+token).execute(); stateDao.insertAll(response.body()); } catch (IOException e) { e.printStackTrace(); } } }); } }
StateViewModel
public class StatesViewModel extends ViewModel { private LiveData<List<State>> states; 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){ states = repo.getStatesFromCountry(countryID); } public LiveData<List<State>> getStates(){ return this.states; } }
Fragment
public class EditAddressFragment extends LifecycleFragment implements View.OnClickListener, Injectable{ private Spinner country, city, state, zip_code; private String token; private List<Country> countries; private List<City> cities; private List<State> states; @Inject ViewModelFactory viewModelFactory; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.addresses_edit_layout, container, false); city = view.findViewById(R.id.city); state = view.findViewById(R.id.state); country = view.findViewById(R.id.country); ... countries = new ArrayList<>(); cities = new ArrayList<>(); states = new ArrayList<>(); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); CountrySpinnerAdapter adapter = new CountrySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, countries); country.setAdapter(adapter); CitySpinnerAdapter cityAdapter = new CitySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, cities); city.setAdapter(cityAdapter); StateSpinnerAdapter stateAdapter = new StateSpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, states); state.setAdapter(stateAdapter); CountriesViewModel countriesViewModel = ViewModelProviders.of(this, viewModelFactory).get(CountriesViewModel.class); countriesViewModel.init(token); countriesViewModel.getCountries().observe(this, adapter::setValues); CityViewModel cityViewModel = ViewModelProviders.of(this, viewModelFactory).get(CityViewModel.class); cityViewModel.init(token); cityViewModel.getCities().observe(this, cityAdapter::setValues); StatesViewModel statesViewModel = ViewModelProviders.of(this, viewModelFactory).get(StatesViewModel.class); statesViewModel.init(token); statesViewModel.getStates().observe(this, states -> { Log.d("called", states.toString()); stateAdapter.setValues(states); } ); country.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { Country c = (Country) adapterView.getItemAtPosition(i); Log.d("cd", c.getId()); //states = new ArrayList<State>(); statesViewModel.getStatesFromCountry(c.getId()); } @Override public void onNothingSelected(AdapterView<?> adapterView) { } });
....
Adapter
public void setValues(List<State> states) { this.states = states; Log.d("s", states.isEmpty()+" "+states.toString()); notifyDataSetChanged(); }
-
Martin Marconcini almost 7 yearsIt’s hard to see the whole picture… (I initially though you had no LiveData) but you do. However, I suggest you check your threading / concurrency, since you’re using an executor in your repo… The approach I’d take (I think) is each spinner is observing LiveData (different) and the ViewModel would ensure to update it when the UI/Spinner1 changes, so the LiveData observed by the UI/Spinner2 changes. If this is what you’re doing atm, it’s hard to see in the code.
-
joao86 almost 7 years@MartinMarconcini I do it in the repository because that is where I have the DAO instance. MY Repository is where I make the calls either to the API or the database. In this case, I only need to go to the local DB because previously I have retrieved all the states. From all my tests, he can apparently get the value from the DB but the onchange method of the observer is not called.
-
joao86 almost 7 years@MartinMarconcini that is the case. I have two different spinners observing two different LiveData. I also saw someone talking about the threading issue, but I am using the same instance of the DAO so there should not be any problem with that I think...
-
Martin Marconcini almost 7 yearsWorth noting that I use all this new crap with RXJava2 as well… tho I highly doubt this has anything to do with anything.
-
Martin Marconcini almost 7 yearsSo the
Log.d("called", states.toString());
stateAdapter.setValues(states); });
callback in the Observable is never called even tho in theory thestatesViewModel.getStates()
live data was updated? hmmm… Another thing I had trouble with was the LifecycleFragment/Activity thing. It wouldn’t correctly work (at least when I tried) so I ended up using/implementingLifecycleRegistryOwner
interface in a simpleAppCompatActivity
(and providing theLifecycleRegistry(this)
-
Martin Marconcini almost 7 yearsbtw, the min reputation for chat is 20 (afaik) I’m sure you can get +2 ;) Go answer a couple of questions! :)))
-
ianhanniballake almost 7 yearsIs your
StatesRepository
a singleton? If you have two instances of your database, events from one won't propagate to observers set on the other instance. -
joao86 almost 7 yearsHi @ianhanniballake, yes it is. I am following the example given here developer.android.com/topic/libraries/architecture/guide.html. Also when I call the
getStates(token)
method, the Observer is updated, so why isn't it updating the other times? My database also has one instance as it also a Singleton. -
joao86 almost 7 yearsSee the continuation of my conversation with @MartinMarconcini, I think the problem is with the observer, since the list values are updated. I thought that the observer would be called every time the list content would be changed, or am I wrong?
-
Cecil Paul over 5 yearsYou can find solution in here. stackoverflow.com/a/46059300/5756743. It worked for me
-
-
joao86 almost 7 yearsLet us continue this discussion in chat. (Moderator note: all prior comments have been archived in chat.)
-
Martin Marconcini almost 7 yearsInteresting. Tho the
allowMainThreadQueries()
shouldn’t really be used for Prod… I’m still puzzled why I have LiveData being returned directly from the DAO and you had to change that… hmmm. Oh well, glad to hear it works. Good luck! -
joao86 almost 7 yearsThe
allowMainThreadQueries
is not mandatory, but for the time I will leave it, just for self-keep :) -
Martin Marconcini almost 7 yearsConsider using RXJava2 for those queries, it’s really useful once you get used to it (and you wouldn’t need to use an Executor, with all the problems that has). :)
-
Anushka Khare about 6 yearsI am using paged list livedata. its builder class returns only livedata . I am not able to replace livedata with mutable livedata , then how can I resolve this issue . LiveData<PagedList<RecipeListPojo>> liveData= new LivePagedListBuilder(recipeDao.getAllRecipesList(simpleSQLiteQuery), 6).build();
-
joao86 about 6 years@AnushkaKhare I haven't tried to use PagedList with LiveData. It's in my to do list :)
-
EdgeDev over 5 years@joao86 Please help me check this similar but different problem stackoverflow.com/questions/54441785/…
-
Bink almost 4 years@joao86 Why did you change the DAO to List instead of LiveData?
-
joao86 almost 4 years@Bink at the time LiveData was only for data that wasn't going to change constantly. If the data was going to change constantly then you needed to use MutableLiveData and therefore couldn't use LiveData. I don't if that has changed in the meantime.
-
Gowtham over 3 yearsthank you sire. was scratching my head around for quite sometime.