LiveData 在第一次回调后删除 Observer

LiveData remove Observer after first callback

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

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

您首先需要确定是否将 LiveDataLifecycleOwner(您的 activity)一起使用。我的假设是您应该使用 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 的回答,您可以简单地调用 removeObserver(this) 来仅删除此观察者,而不是调用 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指的是观察者实例,这只在匿名内部class的情况下有效.如果您使用 lambda,则 this 将引用 activity 实例。

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

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

  1. 第二种方法是

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

现在在你的情况下,你可以通过两种方式移除你的观察者(可能有很多方式),@ToniJoe 在之前的回答中告诉了你一种方式。

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

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

或者,使用 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)
        }
    })
}

我喜欢 Vince and 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() 在观察者被调度时被调用 first-thing,这使得它更安全(即处理从用户的观察者代码中抛出的潜在运行时错误)。

@CommonsWare 和@Toni Joe 提出的解决方案并没有解决我的问题,因为我需要在我的 ViewModel 中收到来自 DAO 查询的第一个结果后删除观察者。但是,在 Livedata keeps observer after calling removeObserer 上找到的以下解决方案凭借我自己的一些直觉为我解决了这个问题。

过程如下,根据请求在您的 ViewModel 中创建一个变量,其中存储 LiveData,在进行空检查后,在 activity 中的创建观察者函数调用中检索它,并调用 remove在导入的 class 中调用 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() 从视图模型中的函数检索数据。从 activity 调用的函数是 getLastMatchNum()。此例程检索 ViewModel 中变量中的数据(通过 DAO 通过存储库检索)。

我的 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);
        }
    }

上面发生的事情是 1.) 我在 activity 的 onCreate 方法中调用 setupLastMatchNumObserver() 例程来更新 class'变量 matchNum。这会跟踪存储在数据库中的游戏中玩家之间的比赛号码。每组球员在数据库中都会有不同的比赛编号,这取决于他们彼此进行新比赛的频率。这个线程中的第一个解决方案对我来说似乎有点厌倦,因为在 onChanged 中调用 remove observers 对我来说似乎很奇怪,并且会在玩家每次移动的每次数据库刷新后不断更改 TextView 对象。所以 matchNumber 在每次移动后都会增加,因为在第一次移动后数据库中有一个新值(即一个 matchNumber++ 值)并且 onChanged 一直被调用,因为 removeObservers 没有按预期工作。 setupLastMatchNumObserver() 检查是否有实时数据的观察者,如果有,则不会在每一轮实例化新调用。如您所见,我正在设置一个 TextView 对象来反映玩家的当前比赛号码。

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

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

我也在 activity 的其他地方以上述方式调用 removeObservers();updateMatchData(data)。美妙之处在于 removeObservers() 可以根据需要多次调用,因为如果没有观察者在场,就会检查 return 。

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

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

   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、将你的耗时方法定义为non-Livedata type inside Viewmodel。您不必在此过程中启动新线程。

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

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

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

首先,我们需要创建Util class方法

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 的地方调用它 class。

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

就这些!

我阅读了一些文档并在观察者那里看到了 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 中设置了触发观察者的“动作”,如果我不这样做,你将以循环观察者触发结束。

这个怎么样:

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

如果您想在稍后阶段再次获取数据,您可以通过这种方式定义更通用的条件。