LiveData не обновляет свое значение после первого вызова

Я бью головой об стену и не могу понять, почему это происходит. Я работаю с новыми архитектурными компонентами для Android, и у меня возникают проблемы с обновлением LiveData со списком объектов. У меня есть два блесны. Когда я изменяю параметр в первом, во втором должен быть изменен его контент. Но этой последней части не происходит. Может кто-нибудь мне помочь?

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;
}

}

Фрагмент

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) {

        }
    });

....

адаптер

public void setValues(List<State> states)
{ 
this.states = states; 
Log.d("s", states.isEmpty()+" "+states.toString()); 
notifyDataSetChanged(); 
}

4 ответа

Решение

Что ж, я нашел решение для этой проблемы и выяснил, как работает этот LiveData.

Спасибо @MartinMarconcini за всю его помощь в отладке;)

Очевидно, что наблюдатели связаны с объектом, для которого вы его сначала настроили. Вы не можете заменить объект (атрибуцией), иначе он не будет работать. Кроме того, если значение вашей переменной изменится, вы должны использовать MutableLiveData

Таким образом, необходимые изменения были:

1. Измените LiveData на MutableLiveData и передайте эту MutableLiveData в хранилище, когда вам необходимо обновить его.

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. В хранилище обновите MutableLiveData, используя 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. Изменен DAO для возврата списка вместо 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.Наконец разрешить выполнять запросы в основном потоке

AppModule.java

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

Дао должен быть одинаковым во всех операциях. Вы используете другой экземпляр Dao для вставки и наблюдения

Не следует обновлять ссылку на живые данные после того, как вы установите ее и начнете наблюдать за ней. Вместо того, чтобы обновлять живые данные с помощью репозитория, вы должны использовать MediatorLiveData.

В вашем случае внесите следующие изменения:

private MediatorLiveData<List<State>> states;  // change
.....
.....
states.addSource(repo.getStatesFromCountry(countryID), newData -> states.setValue(newData)); //change

Написание ответа для лучшего обсуждения.

Итак, у меня есть (в Kotlin, sry) модель, которая представляет собой список заметок (это просто приложение для песочницы, чтобы играть со всем этим), и вот моя архитектура: у меня нет репо, но у меня есть Activity -> ViewModel -> Дао.

Таким образом, Дао подвергает LiveData<MutableList<Note>>

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

Моя ViewModel... выставляет его через:

val notesList = database.notesDao().loadAll()

и моя активность (onCreate) делает...

    viewModel.notesList.observe(this,
            Observer<MutableList<Note>> { notes ->
                if (notes != null) {
                    progressBar?.hide()
                    adapter.setNotesList(notes)
                }
            })

Это работает. Адаптер представляет собой адаптер RecyclerView, который буквально ничего не делает, кроме:

 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)
        }
    }

Если какая-либо другая часть приложения изменяет этот список заметок, адаптер обновляется автоматически. Я надеюсь, что это дает вам площадку, чтобы попробовать простой подход.

Другие вопросы по тегам