英文:
How to remove OnCompleteListener on .get() method when fragment is destroyed?
问题
以下是您要翻译的内容:
我的项目使用Firestore作为数据库。我有一个片段,在打开时会在其onCreate方法中从Firestore请求一个文档。我已经将OnCompleteListener附加到.get()方法,以便在文档获取完成时更新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)
...
以下是我在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);
}
}
});
是否有办法在Fragment的onDestroy()
或onDetach()
方法中移除对OnCompleteListener的回调,以避免这个异常?
英文:
My project uses Firestore as the database. I am having a fragment which when opened requests a document from Firestore in its onCreate method. I have attached an OnCompleteListener to the .get() method so that when the document fetch is complete, it updates UI.
The issue is that sometimes the user, after opening the fragment, quickly moves on to another fragment before the onCompleteListener is triggered. Or in some cases, the same fragment is called twice and while the first instance of the fragment is destroyed, its onCompleteListener is still alive and triggers after the first instance of the Fragment is destroyed. In both these scenarios, I get an exception
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)
Here is my code snippet inside ProfileFragment's onCreate method
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);
}
}
});
Is there a way to remove the callback to OnCompleteListener which I can place in the onDestry()
or onDetach()
method of the Fragment in order to avoid this exception?
答案1
得分: 1
> 如何在片段销毁时从.get()
方法中移除OnCompleteListener
?
无需移除任何侦听器,因为get()
方法仅获取数据一次,然后结束。如果您曾使用过addSnapshotListener()
,您应该根据您的活动的生命周期1来移除侦听器。在您的情况下,onComplete()
方法仅在数据从数据库完全加载完成时触发。您需要知道的是,Cloud Firestore客户端在后台线程中运行所有网络操作。因此,不要在后台线程中处理与上下文相关的事务。
如果您需要实时侦听器,请使用addSnapshotListener()
,并在您的活动/片段停止时停止侦听更改。还请记住,onDestroy
并不总是被调用,因此在onStop()
中停止侦听更改。
编辑:
因为您在回调内部使用了与上下文相关的内容,所以只有在isAdded()
方法返回true
时才应查询数据库。
英文:
> How to remove OnCompleteListener on .get() method when fragment is destroyed?
There is no need to remove any listeners, because the get()
method gets the data exactly once and that's the end of it. If you have been used addSnapshotListener()
, you should have removed the listener according to the life-cycle of your activity. In your case, the onComplete()
method will fire only when the data is completely loaded from the database. What you need to know is that the Cloud Firestore client runs all network operations in a background thread. So don't do things that related to a context in a background thread.
If you need a real-time listener, then use the addSnapshotListener()
and stop listening for changes once your activity/fragment is stoped. Please also remember that onDestroy
is not always called, so stop listening for changes in onStop()
.
Edit:
Because you are using things that are related to a context inside the callback, you should query the database only is isAdded()
method returns true
.
答案2
得分: 1
如果您打算直接在监听器中对视图进行更改(这实际上不是一个好主意,稍后会详细说明),那么您应该在由get()
返回的Task
对象上使用activity-scoped listener,以便在活动停止后不会触发监听器。
documentReference.get().addOnCompleteListener(activity, callback)
请注意,您将活动实例作为第一个参数传递,然后是回调。
然而,对于现代的 Android 应用程序,更好的做法是使用MVVM 架构,正如 Google 在整个 Android 文档中推荐的那样,通过仅暴露 LiveData 来将数据库代码与视图分离,LiveData 能够感知活动的生命周期,在活动停止后不会向观察者发出事件。但这完全取决于您的选择。
英文:
If you are going to make changes to views directly in your listener (which is not really a good idea, more on that later), then you use should an activity-scoped listener on the Task
object returned by get()
, so that the listener won't trigger after the activity becomes stopped.
documentReference.get().addOnCompleteListener(activity, callback)
Note that you're passing the activity instance as the first argument, before the callback.
However, it would be better for modern Android applications to use MVVM architecture, as recommended by Google throughout Android documentation, to abstract away the database code from the views by exposing only a LiveData, which is aware of the activity lifecycle and will not emit events to observers after the activity stops. But that's entirely up to you.
答案3
得分: 0
最简单的方法,我认为可以使用try catch
,这样当您遇到异常时,什么也不会发生,它是一个受控的异常。
解决这个问题的最佳方法是使用MVVM模式与LiveData。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) {
// 将异常记录在日志中,留空或按您喜好处理
}
}
});
英文:
The most easy way I think could be use a try catch
so when you get the exception, nothing happens, it's a controlated exception.
The best way of solve this problems is to use a MVVM pattern with LiveData. ViewModel is aware of the lifeCycle of the activity / fragment and when your fragment dies, you don't get any background notifications from calls you did when the fragment was alive.
Try this
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
}
}
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论