最初の結果を受け取った後にオブザーバーを削除するにはどうすればよいですか?以下に、私が試した2つのコードの方法を示しますが、オブザーバーを削除しても、どちらも更新を受信し続けます。
Observer observer = new Observer<DownloadItem>() {
@Override
public void onChanged(@Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observeForever(observer);
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(downloadItem-> {});
} );
observeForever()
はどのLifecycleOwner
にも関連付けられていないため、最初のものは機能しません。
既存の登録済みオブザーバーをremoveObserver()
に渡していないため、2番目のものは機能しません。
まず、LiveData
(アクティビティ)でLifecycleOwner
を使用しているかどうかを確認する必要があります。私の想定では、LifecycleOwner
を使用する必要があります。その場合は、次を使用します。
Observer observer = new Observer<DownloadItem>() {
@Override
public void onChanged(@Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
CommonsWare answerに続いて、removeObservers()
を呼び出してLiveDataにアタッチされたすべてのオブザーバーを削除する代わりに、単にremoveObserver(this)
を呼び出してこのオブザーバーのみを削除できます。
Observer observer = new Observer<DownloadItem>() {
@Override
public void onChanged(@Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(this);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
注:removeObserver(this)
、this
のは、オブザーバインスタンスを指し、匿名の内部クラスの場合にのみ機能します。ラムダを使用する場合、this
はアクティビティインスタンスを参照します。
Kotlinには拡張機能を備えたより便利なソリューションがあります。
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
この拡張機能により、次のことが可能になります。
liveData.observeOnce(this, Observer<Password> {
if (it != null) {
// do something
}
})
したがって、元の質問に答えるために、それを行うことができます。
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
}
startDownload();
})
元のソースはこちら: https://code.luasoftware.com/tutorials/Android/android-livedata-observe-once-only-kotlin/
更新:@ Hakem-Zaiedは正しいです。observe
の代わりにobserveForever
を使用する必要があります。
上記の@vinceには同意しますが、次のようにlifecycleOwner
を渡すことをスキップし、observerForever
を使用すると思います。
fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
observeForever(object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
または、以下のようにlifecycleOwner
をobserve
と組み合わせて使用します。
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
私は@Vinceと@Hakem Zaiedによる一般的なソリューションが大好きですが、私にとってはラムダバージョンはさらに良いようです:
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner, object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
したがって、次のようになります。
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context) {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
}
startDownload();
}
私はクリーナーを見つけます。
また、removeObserver()
は、オブザーバーがディスパッチされるときに最初に呼び出され、より安全になります(つまり、ユーザーのオブザーバーコード内からの潜在的なランタイムエラースローに対処します)。
同じLifecycleOwnerのObserverのリストから削除したいオブザーバーを取り込むremoveObserver(@NonNull final Observer<T> observer)
(メソッドの名前を注意深く参照してください。これは単数形です)。
removeObservers(@NonNull final LifecycleOwner owner)
(複数形のメソッド名を参照)。このメソッドは、LifecycleOwner自体を取り込み、指定されたLifecycleOwnerのすべてのオブザーバーを削除します。
今、あなたの場合、あなたは2つの方法でオブザーバーを削除することができます(多くの方法があるかもしれません)、1つは前の答えで@ToniJoeによって言われます。
もう1つの方法は、ViewModelにブール値のMutableLiveDataを保持するだけです。これは、初めて観測されたときにtrueを格納し、そのLivedataも同様に観察します。そのため、trueに変わるたびに通知され、そこで特定のオブザーバーを渡すことでオブザーバーを削除できます。
@CommonsWareと@Toni Joeによって提案されたソリューションは、ViewModelのDAOクエリから最初の結果を受け取った後にオブザーバーを削除する必要があるときに、問題を解決しませんでした。しかし、次の解決策は removeObsererを呼び出した後、Livedataがオブザーバーを保持します で、私自身の直感を少し使ってトリックを行いました。
プロセスは次のとおりです。LiveModelが要求に応じて格納されるViewModelで変数を作成し、nullチェックを行った後、アクティビティのcreate observer関数呼び出しで変数を取得し、flushToDBルーチンを呼び出す前にremove observer関数を呼び出しますインポートされたクラス。つまり、私のViewModelのコードは次のようになります。
public class GameDataModel extends AndroidViewModel {
private LiveData<Integer> lastMatchNum = null;
.
.
.
private void initLastMatchNum(Integer player1ID, Integer player2ID) {
List<Integer> playerIDs = new ArrayList<>();
playerIDs.add(player1ID);
playerIDs.add(player2ID);
lastMatchNum = mRepository.getLastMatchNum(playerIDs);
}
public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID) {
if (lastMatchNum == null) { initLastMatchNum(player1ID, player2ID); }
return lastMatchNum;
}
上記で、ViewModelのLiveData変数にデータがない場合、initLastMatchNum()
を呼び出して、ビューモデル内の関数からデータを取得します。アクティビティから呼び出される関数はgetLastMatchNum()
です。このルーチンは、ViewModelの変数のデータを取得します(DAOを介してリポジトリを介して取得されます)。
私のアクティビティにある次のコード
public class SomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
.
.
.
setupLastMatchNumObserver();
.
.
.
}
private void setupLastMatchNumObserver() {
if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers()) {
Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
return;
}
Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer MatchNumber) {
if (MatchNumber == null ) {
matchNumber = 1;
Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
}
else {
matchNumber = MatchNumber; matchNumber++;
Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
}
MatchNumberText.setText(matchNumber.toString());
}
});
}
private void removeObservers() {
final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
if (observable != null && observable.hasObservers()) {
Log.v("removeObserver", "Removing Observers");
observable.removeObservers(this);
}
}
上記で何が行われているのか、1)アクティビティのonCreate
メソッドでsetupLastMatchNumObserver()
ルーチンを呼び出して、クラスの変数matchNum
を更新します。これは、データベースに保存されているゲーム内のプレイヤー間のマッチ番号を追跡します。プレーヤーのすべてのセットは、互いに新しい試合をプレイする頻度に基づいて、データベース内で異なるマッチ番号を持ちます。このスレッドの最初の解決策は、onChanged
でremoveオブザーバーを呼び出すのが奇妙に思え、プレーヤーの各移動のデータベースフラッシュのたびにTextView
オブジェクトを絶えず変更するため、少しうんざりしていました。したがって、最初の移動(つまり、1つのmatchNumber++
値)の後にデータベースに新しい値があり、matchNumber
が意図したとおりに機能しなかったため、onChanged
が呼び出され続けたため、removeObservers
は移動ごとに増分されました。 setupLastMatchNumObserver()
は、ライブデータのオブザーバーが存在するかどうかを確認し、存在する場合は、各ラウンドで新しい呼び出しをインスタンス化しません。ご覧のとおり、TextView
オブジェクトを設定して、プレーヤーの現在のマッチ番号を反映しています。
次の部分は、removeObservers()
を呼び出すタイミングについてのちょっとしたトリックです。最初は、アクティビティのonCreate
オーバーライドでsetupLastMatchNumObserver()
の直後に呼び出すと、すべてうまくいくと思いました。しかし、オブザーバーがデータを取得する前にオブザーバーを削除しました。アクティビティで収集された新しいデータを(アクティビティ全体の別のルーチンで)データベースにフラッシュする呼び出しの直前にremoveObservers()
を呼び出すと、それが魅力のように機能することがわかりました。すなわち、
public void addListenerOnButton() {
.
.
.
@Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data);
}
}
上記の方法で、アクティビティの他の場所でremoveObservers();
とupdateMatchData(data)
を呼び出します。ビューティーはremoveObservers()
オブザーバーがいない場合に戻るチェックがあるため、必要な回数だけ呼び出すことができます。