收到第一个结果后如何删除观察者?以下是我尝试过的两种代码方式,但即使我删除了观察者,它们也都会继续接收更新。
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-> {});
} );
答案 0 :(得分:19)
您的第一个无法使用,因为observeForever()
与任何LifecycleOwner
无关。
你的第二个将无效,因为你没有将现有的注册观察者传递给removeObserver()
。
首先,您需要确定是否使用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);
答案 1 :(得分:13)
在CommonsWare回答之后,您可以简单地调用removeObservers()
来移除此观察者,而不是调用removeObserver(this)
来删除附加到LiveData的所有观察者:
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
引用观察者实例,这仅适用于匿名内部类。如果您使用lambda,则this
将引用活动实例。
答案 2 :(得分:7)
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
。
答案 3 :(得分:3)
我同意上面的@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)
}
})
}
答案 4 :(得分:2)
以下是其他答案中建议的observeOnce
方法的Java版本(一种util类方法,而不是Kotlin扩展函数):
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
@Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}
}
答案 5 :(得分:2)
您正在多次创建实时数据实例(model.getDownloadByContentId(contentId))。
尝试一下:
LiveData myLiveData =model.getDownloadByContentId(contentId);
myLiveData.observe(getViewLifecycleOwner(), downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
myLiveData.removeObservers(getViewLifecycleOwner());
} );
答案 6 :(得分:1)
removeObserver(@NonNull final Observer<T> observer)
(请仔细查看方法的名称,它是单数形式),它将您要从同一LifecycleOwner的观察者列表中删除的观察者纳入其中。
removeObservers(@NonNull final LifecycleOwner owner)
(请参见复数方法名称)。此方法将吸收LifecycleOwner本身,并删除指定LifecycleOwner的所有观察者。
现在,根据您的情况,您可以通过两种方式(可能有很多方式)删除观察者,其中一个由@ToniJoe在上一个答案中告知。
另一种方法是,在ViewModel中仅将布尔值的MutableLiveData设置为布尔值,该值在首次观察时存储true,并且也观察该Livedata。因此,只要它变为true,就会通知您,在那里您可以通过传递特定的观察者来删除观察者。
答案 7 :(得分:1)
我喜欢@Vince和@Hakem Zaied的通用解决方案,但对我来说,lambda版本似乎更好:
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()
在派发观察者时被称为第一件事情,这使其更安全(即应对来自用户观察者代码中的潜在运行时错误引发)。
答案 8 :(得分:1)
observeOnce
方法的 Java 版本已被许多用户推荐。但在这里我们将在主代码中看到实现。
首先,我们需要创建Util类方法
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
@Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}}
现在,我们需要在需要 ViewModel 的地方调用这个类。
LiveDataUtil.observeOnce(viewModel.getUserDetails(), response-> {
if(response.isSuccessful()){
//Do your task
}
}
仅此而已!
答案 9 :(得分:0)
@CommonsWare和@Toni Joe提出的解决方案在我从ViewModel中的DAO查询收到第一个结果后需要删除观察者时,对我来说并没有解决问题。但是,在Livedata keeps observer after calling removeObserer找到的以下解决方案仅凭我自己的直觉就为我成功了。
该过程如下:在ViewModel中创建一个变量,该LiveData应请求存储在该变量中;在执行null检查后,在活动中的创建观察者函数调用中检索它;在调用flushToDB之前,调用移除观察者函数导入类中的例程。也就是说,我的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。)我在活动的setupLastMatchNumObserver()
方法中调用onCreate
例程,以更新类的变量matchNum
。这会跟踪存储在数据库中的游戏中玩家之间的比赛编号。根据每组玩家之间进行新比赛的频率,他们在数据库中的比赛编号将有所不同。这个线程中的第一个解决方案让我感到有些疲倦,因为在onChanged
中调用remove观察者对我来说似乎很奇怪,并且会在每次玩家刷新数据库后每次刷新TextView
对象。因此,matchNumber
在每次移动后都会增加,因为在第一步移动之后数据库中存在一个新值(即一个matchNumber++
值),并且onChanged
一直被调用是因为{{1} }没有按预期工作。 removeObservers
检查是否有实时数据的观察者,如果不存在,则不会在每个回合中实例化新的调用。如您所见,我正在设置一个setupLastMatchNumObserver()
对象以反映玩家的当前比赛人数。
下一部分是何时调用TextView
的小技巧。最初,我认为如果在活动的removeObservers()
覆盖setupLastMatchNumObserver()
之后直接调用它,一切都会好起来的。但是它在观察者可以获取数据之前将观察者移除了。我发现,如果我在调用之前直接调用onCreate
,以将活动中收集的新数据刷新到数据库中(在整个活动中使用单独的例程),那么它就像一个魅力。即
removeObservers()
在我的活动中,我也以上述方式在其他地方呼叫 public void addListenerOnButton() {
.
.
.
@Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data);
}
}
和removeObservers();
。可以根据需要多次调用updateMatchData(data)
,因为如果没有观察者在场,则会进行检查以返回。
答案 10 :(得分:0)
Vince 和 Hakem Zaied 解决方案运行良好,但就我而言,我试图获取 livedata 实例并更新本地数据库,但要先从远程 API 更新 livedata,因此我得到了 NullPointer,所以我切换到observeForever,我能够在更新时获取数据,但现在我必须在获取数据后处理观察者,所以我修改了Vince解决方案,只在livedata包含数据时观察和发射数据。< /p>
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object : Observer<T> {
override fun onChanged(value: T) {
//Resource is my data class response wrapper, with this i was able to
//only update the observer when the livedata had values
//the idea is to cast the value to the expected type and check for nulls
val resource = value as Resource<*>
if (resource.data != null) {
observer(value)
removeObserver(this)
}}
})
}
答案 11 :(得分:0)
这是一个 androidx.lifecycle.Observer Java 示例:
Observer <? super List<MyEntity>> observer = new Observer<List<MyEntity>>() {
@Override
public void onChanged(List<MyEntity> myEntities) {
Log.d(TAG, "observer changed");
MySearchViewModel.getMyList().removeObserver(this);
}
};
MySearchViewModel.getMyList().observe(MainActivity.this, observer);
答案 12 :(得分:0)
在我看来,Livedata 旨在持续接收即将到来的数据。如果您只是希望它只执行一次,例如从服务器请求数据以初始化 UI,我建议您以这种方式设计您的代码:
1、在 Viewmodel 中将耗时的方法定义为 非实时数据 类型。您不必在此过程中启动新线程。
2、在一个Activity中启动一个新线程,并在新线程内调用上面定义的方法,然后在runOnUiThread()
后面编写使用请求数据的逻辑。这样,耗时的方法不会阻塞 UI 线程,而会阻塞新线程,因此 runOnUiThread() 仅在您请求的数据成功接收后运行。
因此,如果您需要,请考虑更换 Livedata。