LiveData does not update its value after the first call

I hit my head against the wall, and I do not understand why this is happening. I am working with new architectural components for Android, and I am having problems updating LiveData with a list of objects. I have two spinners. When I change the parameter in the first, the second should change its contents. But this last part does not occur. Can anybody 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(); } 
+4
source share
2 answers

Well, I reached a solution to this problem and found out how it works in LiveData.

Thanks to @MartinMarconcini for all his help debugging;)

Apparently, the observers are associated with the object with which you first installed it. You cannot replace an object (by attribution) or else it will not work. In addition, if the value of your variable changes, you must use MutableLiveData strong>

Therefore, changes are needed:

1. Go from LiveData to MutableLiveData and transfer 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 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 DAO to return a 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, enable query execution in the main thread

Appmodule.java

 @Singleton @Provides AppDatabase provideDb(Application app) { return Room.databaseBuilder(app, AppDatabase.class,"unitail.db") .allowMainThreadQueries() .fallbackToDestructiveMigration() .build(); } 
+6
source

Writing an answer for a better discussion.

So, I have (in Kotlin, sry) a model that is a list of notes (it’s just a sandbox application to play with all this) and heres my architecture: I don’t have a repo, but I have Activity β†’ ViewModel β†’ Tao .

So Tao provides LiveData<MutableList<Note>>

 @Query("SELECT * FROM notes") fun loadAll(): LiveData<MutableList<Note>> 

My ViewModel ... provides 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) } }) 

It works. An adapter is a RecyclerView adapter that literally does nothing:

  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 application modifies this list of notes, the adapter automatically updates. Hope this gives you a platform to try a simple (r?) Approach.

0
source

Source: https://habr.com/ru/post/1273704/


All Articles