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

Posted

技术标签:

【中文标题】Room:来自 Dao 的 LiveData 将在每次更新时触发 Observer.onChanged,即使 LiveData 值没有变化【英文标题】:Room : LiveData from Dao will trigger Observer.onChanged on every Update, even if the LiveData value has no change 【发布时间】:2018-04-23 06:18:24 【问题描述】:

我发现,只要 DB 中的行更新,Dao 返回的 LiveData 就会调用它的观察者,即使 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 的查询只包括名称字段。然而即使只更新了其他字段,仍会在 Dao Update 上调用observer.onChanged。 (其实如果我不对User实体做任何改动,调用UserDao.updateUser,observer.onChanged还是会被调用)

这是 Dao LiveData in Room 的设计行为吗?是否有机会解决这个问题,以便仅在更新所选字段时调用观察者?


编辑:我更改为使用以下查询将 lastActiveDateTime 值更新为评论建议中的 KuLdip PaTel。用户名的 LiveData 的观察者仍然被调用。

@Query("UPDATE User set lastActiveDateTime = :lastActiveDateTime where id = :id")
void updateLastActiveDateTime(Date lastActiveDateTime, int id);

【问题讨论】:

查看您的@Dao 界面以更新用户,您可以在每次用户未实际更新日期字段时替换用户,添加所有字段。请更改可能对您有帮助的更新查询。 @KuLdipPaTel 请查看编辑。我使用 Query 来更新字段,但仍然得到相同的结果。 @reTs 你的问题解决了吗?我也有,但是解决不了请在您的问题中添加解决方案 【参考方案1】:

Transformations方法distinctUntilChanged有一个简单的解决方案。仅当数据发生更改时才公开新数据。

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

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

但是对于事件情况最好使用这个: https://***.com/a/55212795/9381524

【讨论】:

我正在尝试这个,它说即使添加包后也无法解决..【参考方案2】:

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

【讨论】:

虽然这是我认为是 Room 中的错误的解决方法,但从表中返回每个值并在应用程序中过滤结果是非常低效的。您有效地将整个表保存在内存中。此时连使用数据库还有什么意义? @Pinakin,你也可以用 Java 写代码吗?因为从您的链接中我无法设置解决方案。 @zayn1991 ,当你可以在同一个项目中拥有 kotlin 和 java 时,你会要求它在 java 中。好吧,您可以使用 java 的 distinct 关键字在 java 中实现相同的功能。例如,请参见下面的链接。 geeksforgeeks.org/stream-distinct-java【参考方案3】:

我遇到了同样的问题。

我做错了什么:

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) 多次观察一个 Observe 对象

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

我没有使用不同的功能。在我的情况下,它没有区别。

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

附:库版本

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

【讨论】:

【参考方案4】:

目前没有办法停止触发 Observer.onChanged,这就是为什么我认为 LiveData 对于大多数使用某些连接的查询将毫无用处。 就像@Pinakin 提到的那样,有一个 MediatorLiveData 但这只是一个过滤器,数据仍然会在每次更改时加载。想象一下,在 1 个查询中有 3 个左连接,而您只需要这些连接中的一个或两个字段。如果您每次更新这 4 个表(主 + 3 个连接表)中的任何记录时都实施 PagedList,则将再次调用该查询。 这对于一些数据量很小的表来说是可以的,但如果我错了,请纠正我,这在更大的表的情况下会很糟糕。 如果我们能够通过某种方式将查询设置为仅在更新主表时刷新,或者理想情况下设置为仅在该查询中的字段在数据库中更新时才刷新。

【讨论】:

developer.android.com/reference/kotlin/androidx/lifecycle/…【参考方案5】:

避免可观察查询的误报通知 假设您想根据可观察查询中的用户 id 获取用户:

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

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

如果您的查询返回 LiveData,您可以使用仅允许来自源的不同对象发射的 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 的方法设为公开,并将查询数据库的方法设为受保护。

@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.

【讨论】:

以上是关于Room:来自 Dao 的 LiveData 将在每次更新时触发 Observer.onChanged,即使 LiveData 值没有变化的主要内容,如果未能解决你的问题,请参考以下文章

Room:不确定如何将 Cursor 转换为此方法的返回类型

如何在 Activity 中使用 Room 和 LiveData 删除 LiveData<Object>?

如何使用 Room 和 LiveData 保持 RecyclerView 顺序的更改?

Android LiveData和Room:getValue返回NULL

Android Room LiveData观察器未更新

Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中