How do I remove the observer after I receive the first result? Below are two code ways I've tried, but they both keep receiving updates even though I have removed the observer.
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-> {});
} );
Your first one will not work, because observeForever()
is not tied to any LifecycleOwner
.
Your second one will not work, because you are not passing the existing registered observer to removeObserver()
.
You first need to settle on whether you are using LiveData
with a LifecycleOwner
(your activity) or not. My assumption is that you should be using a LifecycleOwner
. In that case, use:
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);
Following on CommonsWare answer, instead of calling removeObservers()
which will remove all the observers attached to the LiveData, you can simply call removeObserver(this)
to only remove this observer:
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);
Note: in removeObserver(this)
, this
refers to the observer instance and this works only in the case of an anonymous inner class. If you use a lambda, then this
will refer to the activity instance.
There is a more convenient solution for Kotlin with extensions:
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)
}
})
}
This extension permit us to do that:
liveData.observeOnce(this, Observer<Password> {
if (it != null) {
// do something
}
})
So to answer your original question, we can do that:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
}
startDownload();
})
The original source is here: https://code.luasoftware.com/tutorials/android/android-livedata-observe-once-only-kotlin/
Update: @Hakem-Zaied is right, we need to use observe
instead of observeForever
.
I agree with @vince above, but I believe that we either skip passing lifecycleOwner
and use observerForever
as below:
fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
observeForever(object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
or using the lifecycleOwner
with observe
as below:
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)
}
})
}
I love the generic solutions by @Vince and @Hakem Zaied, but to me the lambda version seems even better:
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)
}
})
}
So you end up with:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context) {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
}
startDownload();
}
Which I find cleaner.
Also, removeObserver()
is called first-thing as the observer is dispatched, which makes it safer (i.e. copes with potential runtime error throws from within the user's observer code).
- LiveData class has 2 similar methods to remove Observers. First is named,
removeObserver(@NonNull final Observer<T> observer)
(see carefully the name of the method, it's singular) which takes in the observer you want to be removed from the list of Observers of the same LifecycleOwner.
- Second method is
removeObservers(@NonNull final LifecycleOwner owner)
(see the plural method name). This method takes in the LifecycleOwner itself and removes all the Observers of the specified LifecycleOwner.
Now in your case, you can remove your Observer by 2 ways (there might be many ways), one is told by @ToniJoe in the previous answer.
Another way is just have a MutableLiveData of boolean in your ViewModel which stores true when it's been Observed the first time and just observe that Livedata as well. So whenever it turns to true, you'll be notified and there you can remove your observer by passing that particular observer.
The solution proposed by @CommonsWare and @Toni Joe didn't solve the issue for me when I needed to remove the observers after receiving the first result from a DAO query in my ViewModel. However, the following solution found at Livedata keeps observer after calling removeObserer did the trick for me with a little of my own intuition.
The process is as follows, create a variable in your ViewModel where the LiveData is stored upon request, retrieve it in a create observer function call in the activity after doing a null check, and call a remove observers function before calling the flushToDB routine in an imported class. That is, the code in my ViewModel looks as follows:
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;
}
In the above, if there is no data in the LiveData variable in the ViewModel, I call initLastMatchNum()
to retrieve the data from a function within the view model. The function to be called from the activity is getLastMatchNum()
. This routine retrieves the data in the variable in the ViewModel (which is retrieved via the repository via the DAO).
The following code I have in my Activity
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);
}
}
What's going on in the above, is 1.) I call the setupLastMatchNumObserver()
routine in the onCreate
method of the activity, to update the class's variable matchNum
. This keeps track of the match numbers between players in my game which is stored in a database. Every set of players will have a different match number in the database based upon how often they play new matches with each other. The first solutions in this thread seemed a little weary to me as calling remove observers in the onChanged
seems strange to me and would constantly change the TextView
object after every database flush of each move of the players. So matchNumber
was getting incremented after every move because there was a new value in the database after the first move (namely the one matchNumber++
value) and onChanged
kept being called because removeObservers
was not working as intended. setupLastMatchNumObserver()
checks to see if there are observers of the live data and if so does not instantiate a new call each round. As you can see I am setting a TextView
object to reflect the current matchnumber of the players.
The next part is a little trick on when to call removeObservers()
. At first I thought if I called it directly after setupLastMatchNumObserver()
in the onCreate
override of the activity that all would be fine. But it removed the observer before the observer could grab the data. I found out that if I called removeObservers()
directly prior to the call to flush the new data collected in the activity to the database (in separate routines throughout the activity) it worked like a charm. i.e.,
public void addListenerOnButton() {
.
.
.
@Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data);
}
}
I also call removeObservers();
and updateMatchData(data)
in other places in my activity in the above fashion. The beauty is removeObservers()
can be called as many times as needed since there is a check to return if there are no observers present.
来源:https://stackoverflow.com/questions/47854598/livedata-remove-observer-after-first-callback