104

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

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

14 回答 14

94

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.

于 2019-02-12T11:09:50.307 回答
58

你的第一个将不起作用,因为observeForever()它没有绑定到任何LifecycleOwner.

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

您首先需要确定您是否正在使用LiveDataLifecycleOwner您的活动)。我的假设是您应该使用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);
于 2017-12-18T16:42:22.727 回答
34

按照CommonsWare的回答,而不是调用removeObservers()which 将删除附加到 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);

注意: in removeObserver(this),this指的是观察者实例,这仅适用于匿名内部类的情况。如果您使用 lambda,this则将引用活动实例。

于 2018-04-03T21:32:07.800 回答
34

我喜欢VinceHakem 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()当观察者被调度时,它被称为第一件事,这使得它更安全(即处理用户观察者代码中潜在的运行时错误抛出)。

于 2019-06-06T14:31:57.050 回答
15

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

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

或者,使用lifecycleOwnerwithobserve如下:

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)
        }
    })
}
于 2019-03-03T13:05:17.977 回答
7

这是其他答案中建议的方法的 Java 版本observeOnce(一个 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);
            }
        });
    }

}
于 2020-01-21T16:51:54.393 回答
7

Java 版本的observeOnce方法已经被许多用户推荐。但在这里我们将在主代码中实现。

首先,我们需要创建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
    }
} 

就这样!

于 2021-05-10T17:31:42.400 回答
5

您不止一次地创建实时数据实例 (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());
    } );
于 2020-05-01T07:49:08.197 回答
2

当我在 ViewModel 中收到 DAO 查询的第一个结果后需要删除观察者时,@CommonsWare 和 @Toni Joe 提出的解决方案并没有为我解决问题。但是,在 Livedata 上找到的以下解决方案在调用 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。这会跟踪存储在数据库中的游戏中玩家之间的匹配数。每组球员在数据库中都会有不同的比赛编号,这取决于他们彼此进行新比赛的频率。这个线程中的第一个解决方案对我来说似乎有点厌倦,因为在其中调用 remove 观察者onChanged对我来说似乎很奇怪,并且会TextView在玩家每次移动的每次数据库刷新后不断更改对象。每次移动后都会增加,matchNumber因为在第一次移动后数据库中有一个新值(即一个matchNumber++值)并onChanged一直被调用,因为removeObservers没有按预期工作。setupLastMatchNumObserver()检查是否有实时数据的观察者,如果有,则不会在每一轮实例化一个新调用。如您所见,我正在设置一个TextView对象来反映玩家的当前比赛编号。

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

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

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

于 2019-08-11T04:52:51.323 回答
1

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)
            }}
        })
    }
于 2021-01-20T15:36:00.890 回答
1
  1. LiveData 类有 2 个类似的方法来删除观察者。首先是命名,

removeObserver(@NonNull final Observer<T> observer)(仔细查看方法的名称,它是单数的)它接收要从同一 LifecycleOwner 的观察者列表中删除的观察者。

  1. 第二种方法是

removeObservers(@NonNull final LifecycleOwner owner)(见复数方法名称)。此方法接受 LifecycleOwner 本身并删除指定 LifecycleOwner 的所有观察者。

现在,在您的情况下,您可以通过 2 种方式(可能有多种方式)删除您的观察者,其中一种是 @ToniJoe 在上一个答案中告知的。

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

于 2019-01-22T17:47:20.110 回答
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);
于 2021-02-21T01:16:14.087 回答
0

我阅读了一些文档并在观察者处看到了 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:然后在selectChip观察者中移除观察者:

/**
 * 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 中设置了触发观察者的“动作”,如果我不这样做,你将以循环观察者触发结束。

于 2022-02-23T13:44:02.600 回答
0

在我看来,Livedata 旨在不断接收即将到来的数据。如果您只是希望它只执行一次,例如从服务器请求数据以初始化 UI,我建议您以这种方式设计代码:

1、将您的耗时方法定义为Viewmodel 中的非 Livedata类型。您不必在此过程中启动新线程。

2、在一个Activity中启动一个新的Thread,在这个新的Thread内部,调用上面定义的方法,然后runOnUiThread()在你写你的使用请求数据的逻辑的地方。这样,耗时的方法不会阻塞 UI 线程,而会阻塞新线程,因此 runOnUiThread() 仅在成功接收到您请求的数据后运行。

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

于 2021-03-20T06:48:14.943 回答