30

I found that the LiveData returned by Dao will call its observer whenever the row is updated in DB, even if the LiveData value is obviously not changed.

Consider a situation like the following example :

Example entity

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

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

Somewhere in background thread

UserDao userDao = //.... getting the dao
User user = // obtain from dao....
user.lastActiveDateTime = new Date(); // no change to user.name
userDao.updateUser(user);

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

In this example, the ui is only interested to user name so the query for LiveData only includes the name field. However the observer.onChanged will still be called on Dao Update even only other fields are updated. (In fact, if I do not make any change to User entity and call UserDao.updateUser, the observer.onChanged will still be called)

Is this the designed behaviour of Dao LiveData in Room? Is there any chance I can work around this, so that the observer will only be called when the selected field is updated?


Edit : I changed to use the following query to update the lastActiveDateTime value as KuLdip PaTel in comment suggest. The observer of LiveData of user name is still called.

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

5 回答 5

17

Transformations方法有一个简单的解决方案。distinctUntilChanged只有在数据更改时才公开新数据。

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

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

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

于 2019-03-18T15:30:29.213 回答
10

这种情况被称为观察者的误报通知。请检查链接中提到的第 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
}
于 2017-11-17T12:09:03.890 回答
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) {

        }
    });

在我的例子中,我多次调用了这条线所在的方法。

我认为的文档中,新的观察者从 LiveData 获取数据。正因为如此,onChanged如果作者设置为观察方式,则作者可能会从少数新的匿名观察者那里获得很少的方法userDao.getAllNamesOfUser().observe(this, new 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) 多次观察一个 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);
    }

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

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

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"
于 2019-01-10T01:12:38.063 回答
2

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

于 2019-02-28T07:20:19.933 回答
0

避免可观察查询的误报通知
假设您想根据可观察查询中的用户 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 的方法设为 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()
}

在此处和 Java 中查看更多代码。

于 2019-11-30T19:29:38.070 回答