Комната: 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"