当碎片被销毁时,如何删除.get()方法上的OnCompleteListener



我的项目使用Firestore作为数据库。我有一个片段,当打开它时,它在onCreate方法中请求来自Firestore的文档。我在.get((方法中附加了一个OnCompleteListener,这样当文档获取完成时,它就会更新UI。

问题是,有时用户在打开片段后,会在触发onCompleteListener之前快速转移到另一个片段。或者在某些情况下,同一个片段被调用两次,当片段的第一个实例被销毁时,其onCompleteListener仍然有效,并在片段的第一实例被销毁后触发。在这两种情况下,我都会得到一个异常

Fatal Exception: java.lang.IllegalStateException: Fragment ProfileFragment{4ee762 (a8f7ae01-23be-4d47-b695-68e273b992bf)} not attached to a context.
at androidx.fragment.app.Fragment.requireContext(Fragment.java:774)
at androidx.fragment.app.Fragment.getResources(Fragment.java:838)
at androidx.fragment.app.Fragment.getString(Fragment.java:860)
at com.desivideshi.productinfo.ProfileFragment$18.onComplete(ProfileFragment.java:903)
at com.google.android.gms.tasks.zzj.run(com.google.android.gms:play-services-tasks@@17.1.0:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.google.android.gms.internal.tasks.zzb.dispatchMessage(com.google.android.gms:play-services-tasks@@17.1.0:6)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

以下是ProfileFragment的onCreate方法中的代码片段

documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));

if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout(); 
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}
});

有没有一种方法可以删除对OnCompleteListener的回调,我可以将其放在Fragment的onDestry()onDetach()方法中以避免此异常?

当碎片被销毁时,如何删除.get((方法上的OnCompleteListener?

没有必要删除任何侦听器,因为get()方法只获取一次数据,到此为止。如果您使用过addSnapshotListener(),则应该根据活动的生命周期删除侦听器。在您的情况下,只有当数据完全从数据库加载时,onComplete()方法才会激发。您需要知道的是,Cloud Firestore客户端在后台线程中运行所有网络操作。所以不要在后台线程中做与上下文相关的事情。

如果您需要一个实时侦听器,那么使用addSnapshotListener()并在活动/片段停止后停止侦听更改。还请记住,onDestroy并不总是被调用的,因此请停止侦听onStop()中的更改。

编辑:

因为在回调中使用的是与上下文相关的东西,所以应该只查询数据库。isAdded()方法返回true

如果您要直接在侦听器中更改视图(这不是一个好主意,稍后会详细介绍(,那么您应该在get()返回的Task对象上使用活动范围的侦听器,这样侦听器就不会在活动停止后触发。

documentReference.get().addOnCompleteListener(activity, callback)

请注意,在回调之前,您将活动实例作为第一个参数进行传递。

然而,现代安卓应用程序最好使用MVVM架构,正如谷歌在整个安卓文档中所建议的那样,通过只公开LiveData来从视图中抽象出数据库代码,LiveData知道活动的生命周期,并且在活动停止后不会向观察者发出事件。但这完全取决于你。

我认为最简单的方法是使用try catch,这样当你得到异常时,什么都不会发生,它是一个可控的异常。

解决此问题的最佳方法是在LiveData中使用MVVM模式。ViewModel知道活动/片段的生命周期,当你的片段死亡时,你不会从片段活着时的调用中得到任何后台通知。

试试这个

documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
try{
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));

if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout(); 
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}catch(Exception e){
//Use the exception inside a log, let it blank or as you prefer
}
}
});

最新更新