Room:来自Dao的LiveData将在每次更新时触发Observer.onChanged,即使LiveData值没有变化

时间:2017-11-10 04:24:30

标签: android android-room android-livedata

我发现只要在DB中更新了行,Dao返回的LiveData就会调用它的观察者,即使LiveData值显然没有改变。

考虑以下示例的情况:

示例实体

@Entity
public class User {
    public long id;
    public String name;
    // example for other variables
    public Date lastActiveDateTime;
}

示例Dao

@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);

UI中的某个地方

// 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.
    }
});

在此示例中,ui仅对用户名感兴趣,因此LiveData的查询仅包含名称字段。但是,即使只更新了其他字段,仍会在Dao Update上调用observer.onChanged。 (事实上​​,如果我没有对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);

5 个答案:

答案 0 :(得分:6)

这种情况被称为观察者的误报通知。 请检查link中提到的第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
}

答案 1 :(得分:2)

转换方法distinctUntilChanged中有一个简单的解决方案。仅在更改数据后才公开新数据。

在这种情况下,我们仅在源发生更改时才获取数据:

LiveData<YourType> getData(){
    return Transformations.distinctUntilChanged(LiveData<YourType> source));
}

但是对于事件情况,最好使用以下方法: https://stackoverflow.com/a/55212795/9381524

答案 2 :(得分:0)

我遇到了同样的问题。

我做错了什么:

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

        }
    });

以我为例,我多次调用了该行所在的方法。

From the docs我猜想,新的观察者将从LiveData中获取数据。因此,如果以这种方式设置观察者onChanged,那么作者可能会从很少的新的匿名观察者那里获得很少的userDao.getAllNamesOfUser().observe(this, new Observer方法。

最好在LiveData.observe(...之前创建一次命名的Observer对象

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

我没有使用不同的功能。就我而言,它没有区别。

我希望我的案件能对某人有所帮助。

P.S。库的版本

    // 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"

答案 3 :(得分:0)

当前没有办法停止触发Observer.onChanged,这就是为什么我认为LiveData对于使用某些联接的大多数查询都将无用的原因。 就像提到的@Pinakin一样,这里有一个MediatorLiveData,但这只是一个过滤器,每次更改时数据仍会加载。想象一下,在1个查询中有3个左联接,您只需要这些联接中的一个或两个字段即可。如果您每次在更新这4个表(主表+ 3个联接表)中的任何记录时都实现PagedList,则将再次调用查询。 对于某些数据量较小的表,这是可以的,但是如果我错了,请纠正我,如果表较大,这将是不好的。 最好的办法是,只有在主表更新时,我们才可以将查询设置为刷新,或者在理想情况下,只有在数据库中更新了该查询的字段时,才有刷新方法。

答案 4 :(得分:0)

对于可观察的查询避免误报
假设您要根据可观察查询中的用户ID来获取用户:

@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>

每当该用户更新时,您都会重新发射User对象。但是,当Users表上发生其他更改(删除,更新或插入)而与您感兴趣的User无关时,您也会得到相同的对象,从而导致假阳性通知。更重要的是,如果您的查询涉及多个表,则只要其中任何一个表发生更改,您都会得到新的提示。

如果查询返回LiveData,则可以使用MediatorLiveData,该MediatorLiveData仅允许从源发出不同的对象。

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
}

在您的DAO中,使返回唯一的LiveData的方法public以及查询受保护数据库的方法。

@Dao
 abstract class UserDao : BaseDao<User>() {
   @Query(“SELECT * FROM Users WHERE userid = :id”)
   protected abstract fun getUserById(id: String): LiveData<User>
   fun getDistinctUserById(id: String): 
         LiveData<User> = getUserById(id).getDistinct()
}

See more of the code here and also in Java.