3

Note: Please do not suggest FirebaseUI as an answer.

I am trying to design a chat list UI. For this, I am populating data to my recyclerview which displays the list of chat groups along with last message. I want last message to be updated in real time. Therefore, to do this, I have some listeners/subscription inside onBindViewHolder method which continuously listen for new data and update the view.

The problem what I am facing is if the user migrates to some other activity, the app crashes when chat list data changes in the database. This is because the listeners are still running in background and trying to update views of a destroyed activity.

I am looking for a way to close my listeners/subscriptions when the recyclerview is destroyed. I have tried using onViewDetachedFromWindow but it only works for views that get recycled when the recycler is on screen. If i was reading data only once, i would have cleaned up subscriptions as soon as they complete but my use-case is to continuously listen for changes in data.

Some sample code:

protected void onBindViewHolder(@NonNull ChatViewHolder holder,
                                int position, @NonNull FirebaseConversationRecord model) {
    final CardView cardView = (CardView) holder.itemView;
    ...
    final ValueEventListener listener = new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            Log.v("FIREBASEADAPTERLISTENER", dataSnapshot.getKey());
            FirebaseUserRecord data = dataSnapshot.getValue(FirebaseUserRecord.class);
            textViewName.setText(data.getName());

            GlideApp.with(cardView.getContext())
                    .load(data.getProfilePicURL())
                    .into(imageView);
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
            Log.v("FIREBASEADAPTERLISTENER", databaseError.getMessage());
        }
    };
    ...
 }

EDIT This question is in context of a RecyclerView and how to attach listeners during onBindView. It is not the same as adding/removing a single listener from an activity which is very straight forward to implement.

Adi
  • 4,149
  • 4
  • 25
  • 41
  • Please check the duplicate to see how you can remove the listener according to the life-cycle of your activity. – Alex Mamo Nov 22 '18 at 08:50
  • If you consider at some point to try using [Cloud Firestore](https://firebase.google.com/docs/firestore/), here you can find a tutorial on how to can create a functional [Chat App](https://www.youtube.com/playlist?list=PLn2n4GESV0Ak1HiH0tTPTJXsOEy-5v9qb). – Alex Mamo Nov 22 '18 at 08:51
  • 1
    I am amazed how to see the duplicate linked question. I cannot understand how removing valueEventListener in an activity is same as removing it from recyclerview onBindView. By this logic you should mark all questions containing the keyword "valueeventlistener" and "remove" as duplicate. This is poor moderation according to me. – Adi Nov 22 '18 at 15:09
  • It is the exact same thing. That's why I have marked it as a duplicate. Doesn't matter in which part of your acitivity you are adding the `ValueEventListener`, you should remove it according to the lyfe-cycle of your activity. Btw, using RecyclerView's `onBindViewHolder` method, it means that you are still in an activity and should be removed or, as in the duplicate, use a `ListenerForSingleValueEvent`, which adds a listener only once, right? – Alex Mamo Nov 22 '18 at 16:01
  • 1
    I don't see it how is it same. Probably, it will be great and maybe beneficial to future noob users like me if you can answer the question with a code example. – Adi Nov 22 '18 at 17:41
  • The code example is right in the duplicate. – Alex Mamo Nov 23 '18 at 08:53
  • 2
    For any other user struggling with this question, the way i finally implemented is by doing two things. First in `onViewDetachedFromWindow` i detached the listener. Second in the onDestroy() of activity, i used `child = recyclerView.getChildAt(i); holder = recyclerView.getChildViewHolder(child)`. In the viewholder, i have implemented a `removeListener` which i am calling as `holder.removeListener`. – Adi Nov 23 '18 at 16:37
  • You could pass the valueevent listener via constructor to the recycler view adapter, and then in your activity you could handle it in onPause, and then onResume you could reattach the listener, haven't tried it but just thinking aloud, also this question is not a duplicate I agree with you.. – DragonFire Apr 21 '20 at 09:21
  • But the problem is that if you are using the recycler view adapter for multiple activities/fragments then you will have to pass listener from each one of them and then handle that - extra work.. – DragonFire Apr 21 '20 at 09:25
  • @Adi with your answer ensure that you have recyclerview.setAdapter(null) in onPause of activity, sometimes the onViewDetachedWindow does not get called... – DragonFire Apr 21 '20 at 09:33

2 Answers2

0

remove ValueEvenetListener from DatabaseReference onDestroy using this following method

DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("");
    ValueEventListener valueEventListener = new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            //here you gets the data
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
            //if some error occurs
        }
    };

    //add value event listener
    databaseReference.addValueEventListener(valueEventListener);

    //remove enet listener
    databaseReference.removeEventListener(valueEventListener);

this is just a hint

ok do this way :

//here you require the instance of activity
//in your onDataCahnge method 
if(activity.getApplicationContext() != null){
   //implement all the ui change here
}else{
  //here you remove the ValueEventListener
  databaseReference.removeEventListener(valueEventListener);
}
Harkal
  • 1,770
  • 12
  • 28
  • 1
    This is a wrong answer. The question is in context of a RecyclerView and not an activity. There is one ValueEventListener bound to each viewHolder/item in the recyclerview. I have to clean all of it. – Adi Nov 21 '18 at 22:20
  • you said it craches because the eventlistener still holding the destroyed views sp in that case activity life cycle is involved. ok leme give you more explaination how you can resolve the issue. wait a minute – Harkal Nov 21 '18 at 22:22
  • @Adi have a look at the update bro! – Harkal Nov 21 '18 at 22:44
  • The challenge with recycler view is that there will be one valueEventListener for each item in the recycler. I need a mechanism to consciously close down all of them. On way is that i keep a List to track all the referenced recycler but i was hoping for a simpler way like `onViewDetachedFromWindow`. Your solution is only accounting for a very simple case. Please take complexities of recycler in account. It will be really helpful to me. – Adi Nov 22 '18 at 15:14
  • the last approach i told u will work in your case too – Harkal Nov 22 '18 at 17:31
  • in all your viewholders before rendering data just check if the context is availabe. if it availbe you can render data else detach the listner from database refernce – Harkal Nov 22 '18 at 17:32
  • This is classic way of creating memory leak. What if no new data is added? This means the listener will forever be alive. Good hack though but need a proper way. – Adi Nov 22 '18 at 18:21
  • then the approach i told u at first is the best – Harkal Nov 22 '18 at 18:35
  • maintain a listener arraylist and as when u close the activity then trigger the function in interface so that it notifies all the vent listeners and remove them from receiveing data – Harkal Nov 22 '18 at 18:35
  • or what more you can do is use rxAndroid – Harkal Nov 22 '18 at 18:36
0

Pass in or expose an instance of CompositeDisposable and clear your disposables in the onDestroy() of your Activity.

urgentx
  • 3,832
  • 2
  • 19
  • 30