Daoから返されたLiveDataは、DBで行が更新されるたびに、その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更新で呼び出されます。 (実際、Userエンティティに変更を加えずにUserDao.updateUserを呼び出すと、observer.onChangedが呼び出されます)
これは、部屋のDao LiveDataの設計された動作ですか?選択したフィールドが更新されたときにのみオブザーバーが呼び出されるように、これを回避できる可能性はありますか?
編集:コメントのKuLdip PaTelが示唆するように、lastActiveDateTime値を更新するために次のクエリを使用するように変更しました。ユーザー名のLiveDataのオブザーバーは引き続き呼び出されます。
@Query("UPDATE User set lastActiveDateTime = :lastActiveDateTime where id = :id")
void updateLastActiveDateTime(Date lastActiveDateTime, int id);
この状況は、オブザーバーの誤検知通知として知られています。このような問題を回避するには、 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
}
Transformationsmethod distinctUntilChanged
。exposeには、データが変更された場合にのみ新しいデータを公開する簡単なソリューションがあります。
この場合、ソースが変更されたときにのみデータを取得します。
LiveData<YourType> getData(){
return Transformations.distinctUntilChanged(LiveData<YourType> source));
}
しかし、イベントの場合はこれを使用する方が適切です: https://stackoverflow.com/a/55212795/9381524
現在、Observer.onChangedのトリガーを停止する方法はありません。そのため、一部の結合を使用しているほとんどのクエリではLiveDataは役に立たないと思います。 @Pinakinが言及したように、MediatorLiveDataがありますが、これは単なるフィルターであり、データはすべての変更でロードされます。 1つのクエリに3つの左結合があり、それらの結合からフィールドまたは2つだけが必要な場合を想像してください。これら4つのテーブル(メイン+ 3つの結合テーブル)のレコードが更新されるたびにPagedListを実装する場合、クエリが再度呼び出されます。これは、データ量の少ない一部のテーブルでは問題ありませんが、テーブルが大きい場合にこれが間違っている場合は修正してください。メインテーブルが更新された場合にのみ更新されるようにクエリを設定する方法があれば、またはクエリ内のフィールドがデータベースで更新された場合にのみ更新される方法が理想的です。
私は同じ問題で立ち往生しました。
私が間違ったこと:
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からデータを取得すると推測しました。そのため、そのように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)1つのObserveオブジェクトを複数回観察する
_public void callMethodMultipleTimes(String searchText) {
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
_
このメソッドを複数回呼び出してデバッグすると、callMethodMultipleTimes();
を呼び出したのと同じ回数observer
を追加していることがわかりました。
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"
_