LiveData在第一次回调后删除Observer

时间:2017-12-17 11:26:12

标签: android observers android-room android-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).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-> {});
        } );

13 个答案:

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

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

答案 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)

  1. LiveData类具有2个相似的方法来删除观察者。首先被命名为

removeObserver(@NonNull final Observer<T> observer)(请仔细查看方法的名称,它是单数形式),它将您要从同一LifecycleOwner的观察者列表中删除的观察者纳入其中。

  1. 第二种方法是

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。