11

I have a list of chat rooms for a given user at one location, and total number of messages for a given chat room at another location. I want to keep track of a number of messages at chatrooms user belongs to.

I have a following snippet:

//getting all chat rooms this user belongs to
mFirebase.child("myChatRooms").child("testUser").addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded (DataSnapshot dataSnapshot, String previousKey){
        for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
            ChatRoom myRoom = postSnapshot.getValue(ChatRoom.class);

            //listening to  message count in every chat room user belongs to
            mFirebase.child("chatRoomMessageCount").child(postSnapshot.getKey()).addValueEventListener(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                        //number of messages have changed
                }

                @Override
                public void onCancelled(FirebaseError firebaseError) {

                }
            });
        }
    }

    //......
}

The question is, how do I remove all those ValueEventListener's later, when onChildRemoved will be called, or I won't need them anymore?

What is the recommended approach in dealing with this situation? Should I store child key and listener in HashMap and keep track of them myself or there is some way to remove all listeners for a given firebase location?

Sver
  • 3,349
  • 6
  • 32
  • 53

1 Answers1

23

A data conscientious way would be to create a HashMap of ValueEventListeners and then in onStop or onDestroy you would remove those listeners.

To keep things simple, you should use a single event listener to get the rooms. Then for each room you can create a realtime listener.

public class MainActivity extends Activity {

    private Firebase mRef;
    private HashMap<Firebase, ValueEventListener> mListenerMap;

    @Override
    protected void onStart() {
        super.onStart();

        mRef = new Firebase("https://<your-firebase-app>/myChatRooms/testUser");

        mRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {

                    ValueEventListener listener = new ValueEventListener() {
                        @Override
                        public void onDataChange(DataSnapshot dataSnapshot) {
                            // do your thing here
                        }

                        @Override
                        public void onCancelled(FirebaseError firebaseError) {

                        }
                    };
                    mListenerMap.put(postSnapshot.getRef(), listener);
                    Firebase childRef = mRef.child("chatRoomMessageCount").child(postSnapshot.getKey());
                    childRef.addValueEventListener(listener);
                }
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        for (Map.Entry<Firebase, ValueEventListener> entry : mListenerMap.entrySet()) {
            Firebase ref = entry.getKey();
            ValueEventListener listener = entry.getValue();
            ref.removeEventListener(listener);
        }
    }
}
David East
  • 31,526
  • 6
  • 67
  • 82
  • 1
    That's basically what I had in mind. I was really hoping there would be a more elegant way of doing this, or similar functionality built in already. For example we could pass TAG as a second parameter to addValueEventListener, and then just call something like removeValueEventListenersWithTag. But, I guess given the real time and continuous nature of firebase you guys decided to leave it to developers, which is ok, but you have to reflect it in docs. Flat data often leads to nested listeners, and it could be confusing for people new to the concept. – Sver Nov 19 '15 at 00:04
  • 3
    I agree. I'll do more research into a more elegant solution. I really appreciate your feedback! – David East Nov 19 '15 at 00:07
  • Does this meet the requirements of an accepted answer for you? Or are there any other considerations? – David East Nov 19 '15 at 05:31
  • @DavidEast so does adding SingleEventListener solve the issue of having to recycle/remove the listeners, because I just need to call them once to grab the data, if so I can easily just use that. – Lion789 Oct 28 '16 at 03:13
  • 4
    @Lion789 Yep! There is no need to remove a `SingleEventListener` as they remove themselves after the data first downloads. – David East Oct 28 '16 at 12:10
  • @DavidEast how about for mDatabase.child("projects").orderByChild("viewCount").limitToLast(15).addChildEventListener(new ChildEventListener() { do I need to remove the event listener? – Lion789 Nov 03 '16 at 03:18
  • @DavidEast Grate answer thanks. Can you give some link to open source demo or production code using this Technic? I want to learn how this can look like IRL. – Erik Nov 27 '16 at 19:31
  • 3
    The HashMap needs to be initialized before the `put` method will work. A NullPointerException will be thown otherwise. The line `private HashMap mListenerMap;` should be updated to `private HashMap mListenerMap = new HashMap();` – Shaun Dychko Aug 04 '17 at 23:35