实时数据在第一次回调后删除观察者



收到第一个结果后如何删除观察器?以下是我尝试过的两种代码方法,但即使我删除了观察器,它们也会继续接收更新。

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-> {});
} );

对于带有扩展的 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

您的第一个将不起作用,因为observeForever()与任何LifecycleOwner无关。

您的第二个将不起作用,因为您没有将现有的注册观察者传递给removeObserver()

您首先需要确定您是否使用带有LifecycleOwner(您的活动)的LiveData。我的假设是您应该使用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);

我喜欢 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(context as AppCompatActivity) {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
}
startDownload();
}

我觉得更干净。

此外,removeObserver()在调度观察器时被称为第一件事,这使得它更安全(即应对用户观察者代码中潜在的运行时错误抛出)。

按照 CommonsWare 的回答,您可以简单地调用removeObserver(this)来删除所有附加到 LiveData 的观察者,而不是调用removeObservers()来删除所有附加到 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将引用活动实例。

我同意上面的文斯,但我相信我们要么跳过传递lifecycleOwner并使用observerForever如下:

fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
observeForever(object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}

或者,将lifecycleOwnerobserve一起使用,如下所示:

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)
}
})
}

许多用户已经建议使用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);
}
});
}}

现在,我们需要在需要视图模型的地方调用这个类。

LiveDataUtil.observeOnce(viewModel.getUserDetails(), response-> {
if(response.isSuccessful()){
//Do your task
}
} 

就这样!

以下是其他答案中建议的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);
}
});
}
}

您正在多次创建实时数据实例(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());
} );

@CommonsWare 和 @Toni Joe 提出的解决方案并没有为我解决问题,当我在 ViewModel 中收到来自 DAO 查询的第一个结果后需要删除观察者时。但是,在调用 removeObserer 后,在 Livedata 上找到的以下解决方案保持观察者用我自己的一点直觉为我做了这个伎俩。

该过程如下所示:在 ViewModel 中创建一个变量,其中根据请求存储 LiveData,在执行空检查后在活动的创建观察器函数调用中检索该变量,并在导入的类中调用 flushToDB 例程之前调用删除观察器函数。也就是说,我的视图模型中的代码如下所示:

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中调用删除观察者对我来说似乎很奇怪,并且在玩家每次移动的数据库刷新后都会不断更改TextView对象。因此,matchNumber每次移动后都会递增,因为在第一次移动后数据库中有一个新值(即matchNumber++值),并且onChanged不断被调用,因为removeObservers没有按预期工作。setupLastMatchNumObserver()检查是否存在实时数据的观察者,如果有,则不会每轮实例化一个新调用。如您所见,我正在设置一个TextView对象来反映玩家的当前比赛编号。

下一部分是关于何时打电话给removeObservers()的小技巧。起初,我认为如果我在setupLastMatchNumObserver()活动onCreate覆盖后直接调用它,一切都会好起来的。但它在观察者获取数据之前删除了观察者。我发现,如果我在调用之前直接调用removeObservers()将活动中收集的新数据刷新到数据库(在整个活动中的单独例程中),它就像一个魅力。即,

public void addListenerOnButton() {
.
.
.
@Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data); 
}
}

我也以上述方式在其他地方打电话给removeObservers();updateMatchData(data)。美女是removeObservers()可以根据需要多次调用,因为如果没有观察员在场,就会检查返回。

  1. LiveData 类有 2 种类似的方法来删除观察者。首先被命名,

removeObserver(@NonNull final Observer<T> observer)(仔细查看方法的名称,它是单数的),它包含您希望从同一生命周期所有者的观察者列表中删除的观察者。

  1. 第二种方法是

removeObservers(@NonNull final LifecycleOwner owner)(请参阅复数方法名称)。此方法采用生命周期所有者本身,并删除指定生命周期所有者的所有观察者。

现在,在您的情况下,您可以通过两种方式(可能有很多方法)删除您的观察者,一种由上一个答案中的@ToniJoe告诉。

另一种方法是在你的ViewModel中有一个布尔值的MutableLiveData,当它第一次被观察到时,它存储true,并且也只是观察Livedata。因此,每当它变为 true 时,您都会收到通知,并且您可以通过传递该特定观察者来删除您的观察者。

Vince 和 Hakem Zaied 解决方案运行良好,但就我而言,我试图获取 livedata 实例并更新本地数据库,但 livedata 首先要从远程 API 更新,因此我得到了一个 NullPointer,所以我切换到 observeForever,并且能够在更新数据时获取数据, 但是现在我必须在获取数据后处理观察器,所以我修改了 Vince 解决方案,只在 livedata 包含数据时才观察和发出数据。

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)
}}
})
}

下面是一个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);

在我看来,Livedata旨在持续接收即将到来的数据。如果你只是希望它只执行一次,比如说,从服务器请求数据来初始化UI,我建议你这样设计你的代码:

1、在视图模型中将耗时的方法定义为非实时数据类型。在此过程中,您不必启动新线程。

2、在活动中启动一个新线程,在新线程中,调用上面定义的方法,然后是runOnUiThread(),在其中编写利用所请求数据的逻辑。以这种方式,耗时的方法不会阻塞 UI 线程,而它会阻塞新线程,因此 runOnUiThread() 仅在成功接收请求的数据后运行。

因此,请考虑替换Livedata,如果这是您想要的。

我阅读了一些文档并在观察者处看到了 remove 方法,所以我想到了这个解决方案:

1:首先声明观察者:

// observer for selecting chip in view
View actionView;
Observer selectChipFunction = (action) -> selectChip(actionView, action);

2:然后使用观察器:

// select an action if set before
if (sharedViewModel.getAction() != null)
sharedViewModel.getAction().observe(getViewLifecycleOwner(), selectChipFunction);


3:然后在选择芯片观察器中移除观察器:

/**
* select action chip
* @param actionView - view to use for selecting action chip
* @param actionObject - action chip to select
*/
private void selectChip(View actionView, Object actionObject)
{
// no need for observing when action is changed so remove.
sharedViewModel.getAction().removeObserver(selectChipFunction);

这样它只触发一次,然后删除。就我而言,我需要这个,因为我正在 selectChipFunction 中设置触发观察器的"动作",如果我不这样做,您将以循环观察器触发结束。

这个怎么样:

fun <T> LiveData<T>.observeOnCondition(lifecycleOwner: LifecycleOwner,
observer: Observer<T>,
condition: () -> Boolean) {
observe(lifecycleOwner) { t ->
if (condition()) {
observer.onChanged(t)
}
}
}

这样,如果您希望在稍后阶段再次选取数据,则可以定义更通用的条件。

您可以使用这样的函数观察一次,然后删除观察者

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
var ob: Observer<T>? = null
ob = Observer { value ->
ob?.let {
removeObserver(it)
}
observer.onChanged(value)
}
observe(lifecycleOwner, ob)
}

不幸的是,所有解决方案都不适合我。唯一对我有用的例子是那个链接。

https://gist.github.com/kobeumut/edb3edd9a2ae9abf6984a42bb2de0441

最新更新