简体   繁体   中英

How to remove OnCompleteListener on .get() method when fragment is destroyed?

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?

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 .

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM