web-dev-qa-db-ja.com

ルーム:DaoのLiveDataは、LiveData値に変更がない場合でも、更新ごとにObserver.onChangedをトリガーします

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

この状況は、オブザーバーの誤検知通知として知られています。このような問題を回避するには、 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
}
7
Pinakin

Transformationsmethod distinctUntilChanged。exposeには、データが変更された場合にのみ新しいデータを公開する簡単なソリューションがあります。

この場合、ソースが変更されたときにのみデータを取得します。

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

しかし、イベントの場合はこれを使用する方が適切です: https://stackoverflow.com/a/55212795/9381524

3
Jurij Pitulja

現在、Observer.onChangedのトリガーを停止する方法はありません。そのため、一部の結合を使用しているほとんどのクエリではLiveDataは役に立たないと思います。 @Pinakinが言及したように、MediatorLiveDataがありますが、これは単なるフィルターであり、データはすべての変更でロードされます。 1つのクエリに3つの左結合があり、それらの結合からフィールドまたは2つだけが必要な場合を想像してください。これら4つのテーブル(メイン+ 3つの結合テーブル)のレコードが更新されるたびにPagedListを実装する場合、クエリが再度呼び出されます。これは、データ量の少ない一部のテーブルでは問題ありませんが、テーブルが大きい場合にこれが間違っている場合は修正してください。メインテーブルが更新された場合にのみ更新されるようにクエリを設定する方法があれば、またはクエリ内のフィールドがデータベースで更新された場合にのみ更新される方法が理想的です。

1
SubK24

私は同じ問題で立ち往生しました。

私が間違ったこと:

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"
_
0
zayn1991