Комната: LiveData от Dao запускает Observer.onChanged для каждого обновления, даже если значение LiveData не имеет изменений
Я обнаружил, что LiveData, возвращаемый Dao, будет вызывать своего наблюдателя всякий раз, когда строка обновляется в БД, даже если значение LiveData, очевидно, не изменяется.
Рассмотрим ситуацию, подобную следующему примеру:
Пример сущности
@Entity
public class User {
public long id;
public String name;
// example for other variables
public Date lastActiveDateTime;
}
Пример Дао
@Dao
public interface UserDao {
// I am only interested in the user name
@Query("SELECT name From User")
LiveData<List<String>> getAllNamesOfUser();
@Update(onConflict = OnConflictStrategy.REPLACE)
void updateUser(User user);
}
Где-то в фоновом потоке
UserDao userDao = //.... getting the dao
User user = // obtain from dao....
user.lastActiveDateTime = new Date(); // no change to user.name
userDao.updateUser(user);
Где-то в интерфейсе
// omitted ViewModel for simplicity
userDao.getAllNamesOfUser().observe(this, new Observer<List<String>> {
@Override
public void onChanged(@Nullable List<String> userNames) {
// this will be called whenever the background thread called updateUser.
// If user.name is not changed, it will be called with userNames
// with the same value again and again when lastActiveDateTime changed.
}
});
В этом примере пользовательский интерфейс интересует только имя пользователя, поэтому запрос LiveData включает только поле имени. Однако Observer.onChanged будет по-прежнему вызываться при обновлении Dao, даже если обновляются только другие поля. (На самом деле, если я не внесу никаких изменений в сущность User и вызову UserDao.updateUser, по-прежнему будет вызываться Observer.onChanged)
Это спроектированное поведение Dao LiveData в комнате? Есть ли шанс, что я смогу обойти это, так что наблюдатель будет вызываться только при обновлении выбранного поля?
Изменение: я изменил, чтобы использовать следующий запрос для обновления значения lastActiveDateTime, как KuLdip PaTel в комментарии предлагают. Наблюдатель LiveData имени пользователя по-прежнему вызывается.
@Query("UPDATE User set lastActiveDateTime = :lastActiveDateTime where id = :id")
void updateLastActiveDateTime(Date lastActiveDateTime, int id);
Ответы
Ответ 1
Эта ситуация известна как ложноположительное уведомление наблюдателя.
Пожалуйста, отметьте номер 7, указанный в ссылке чтобы избежать такой проблемы.
Ниже пример написан в kotlin, но вы можете использовать его версию java, чтобы заставить его работать.
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}
Ответ 2
Я застрял с той же проблемой.
Что я сделал не так:
1) создание анонимного объекта:
private LiveData<List<WordsTableEntity>> listLiveData;
// listLiveData = ... //init our LiveData...
listLiveData.observe(this, new Observer<List<WordsTableEntity>>() {
@Override
public void onChanged(@Nullable List<WordsTableEntity> wordsTableEntities) {
}
});
В моем случае я несколько раз вызывал метод, в котором находилась эта строка.
Из документов я предположил, что новые наблюдатели берут данные из LiveData. Из-за этого автор мог получить несколько методов onChanged
от нескольких новых анонимных наблюдателей, если он установил наблюдение userDao.getAllNamesOfUser().observe(this, new Observer
.
Будет лучше создать именованный объект Observer до LiveData.observe(...
и один раз
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
@Override
public void onChanged(@Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
и затем установите его LiveData.observe(observer
и мы получаем данные из LieData в первый раз, а затем, когда данные будут изменены.
2) Наблюдение за одним объектом наблюдения несколько раз
public void callMethodMultipleTimes(String searchText) {
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
Я вызывал этот метод несколько раз, и отладка показала мне, что я добавлял своего observer
столько раз, сколько я вызывал callMethodMultipleTimes();
Наш listLiveData
является глобальной переменной, и она живет. Это меняет ссылку на объект здесь
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
но старый объект в памяти не удаляется сразу
Это будет исправлено, если мы вызовем listLiveData.removeObserver(observer);
до
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
И возвращаясь к 1) - мы не можем вызвать listLiveData.removeObserver(our anonimous Observer);
потому что у нас нет анонимной ссылки на объект.
Итак, в результате мы можем сделать так:
private Observer observer;
private LiveData<List<WordsTableEntity>> listLiveData;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
@Override
public void onChanged(@Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
public void searchText(String searchText) {
if (listLiveData != null){
listLiveData.removeObservers(this);
}
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
Я не использовал отдельные функции. В моем случае это работает без внятных.
Я надеюсь, что мое дело кому-то поможет.
PS версия библиотек
// Room components
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"