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
我发现 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);
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 的查询仅包含名称字段。但是 observer.onChanged 仍然会在 Dao Update 上被调用,即使只有其他字段被更新。
(实际上,如果我不对 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);
这种情况称为观察者误报通知。
请检查 link 中提到的第 7 点以避免此类问题。
Below example is written in kotlin but you can use its java version to get it work.
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) 创建匿名对象:
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 获取数据。正因为如此,如果他以这种方式设置观察 userDao.getAllNamesOfUser().observe(this, new Observer
,作者可能会从几个新的匿名观察者那里收到很少的 onChanged
方法。
最好在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"
目前没有办法停止触发 Observer.onChanged 这就是为什么我认为 LiveData 对于大多数使用某些连接的查询来说是无用的。
就像 @Pinakin 提到的那样,有一个 MediatorLiveData 但这只是一个过滤器,数据仍然会在每次更改时加载。想象一下,在 1 个查询中有 3 个左联接,而您只需要这些联接中的一两个字段。如果您每次更新这 4 个 tables(main + 3 个加入的 tables)中的任何记录时实施 PagedList,将再次调用查询。
这对于某些 tables 的数据量很小是可以的,但如果我错了请纠正我,这在 tables 更大的情况下会很糟糕。
最好是我们有某种方法可以将查询设置为仅在主 table 更新时才刷新,或者理想情况下只有当该查询的字段在数据库中更新时才刷新。
Transformations 方法 distinctUntilChanged
中有一个简单的解决方案。仅当数据发生变化时才公开新数据。
在这种情况下,我们仅在数据源发生变化时才获取数据:
LiveData<YourType> getData(){
return Transformations.distinctUntilChanged(LiveData<YourType> source));
}
但是对于事件情况最好使用这个:
避免可观察查询的误报通知
假设您想根据可观察查询中的用户 ID 获取用户:
@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
每当该用户更新时,您将获得 User
对象的新发射。但是当 Users
table 上发生与您感兴趣的 User
无关的其他更改(删除、更新或插入)时,您也会得到相同的对象,结果在误报通知中。更重要的是,如果您的查询涉及多个 table,只要其中任何一个发生变化,您就会得到一个新的发射。
如果您的查询 returns 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 中,使 returns 不同 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()
}
我发现 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);
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 的查询仅包含名称字段。但是 observer.onChanged 仍然会在 Dao Update 上被调用,即使只有其他字段被更新。 (实际上,如果我不对 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);
这种情况称为观察者误报通知。 请检查 link 中提到的第 7 点以避免此类问题。
Below example is written in kotlin but you can use its java version to get it work.
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) 创建匿名对象:
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 获取数据。正因为如此,如果他以这种方式设置观察 userDao.getAllNamesOfUser().observe(this, new Observer
,作者可能会从几个新的匿名观察者那里收到很少的 onChanged
方法。
最好在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);
}
我没有使用不同的函数。在我的例子中,它没有区别。
我希望我的案例对某人有所帮助。
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"
目前没有办法停止触发 Observer.onChanged 这就是为什么我认为 LiveData 对于大多数使用某些连接的查询来说是无用的。 就像 @Pinakin 提到的那样,有一个 MediatorLiveData 但这只是一个过滤器,数据仍然会在每次更改时加载。想象一下,在 1 个查询中有 3 个左联接,而您只需要这些联接中的一两个字段。如果您每次更新这 4 个 tables(main + 3 个加入的 tables)中的任何记录时实施 PagedList,将再次调用查询。 这对于某些 tables 的数据量很小是可以的,但如果我错了请纠正我,这在 tables 更大的情况下会很糟糕。 最好是我们有某种方法可以将查询设置为仅在主 table 更新时才刷新,或者理想情况下只有当该查询的字段在数据库中更新时才刷新。
Transformations 方法 distinctUntilChanged
中有一个简单的解决方案。仅当数据发生变化时才公开新数据。
在这种情况下,我们仅在数据源发生变化时才获取数据:
LiveData<YourType> getData(){
return Transformations.distinctUntilChanged(LiveData<YourType> source));
}
但是对于事件情况最好使用这个:
避免可观察查询的误报通知
假设您想根据可观察查询中的用户 ID 获取用户:
@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
每当该用户更新时,您将获得 User
对象的新发射。但是当 Users
table 上发生与您感兴趣的 User
无关的其他更改(删除、更新或插入)时,您也会得到相同的对象,结果在误报通知中。更重要的是,如果您的查询涉及多个 table,只要其中任何一个发生变化,您就会得到一个新的发射。
如果您的查询 returns 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 中,使 returns 不同 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()
}