3

I have setup a value event listener in Activity's onCreate method. In that method I am logging the data to console. I opened the activity and everything works fine. But if I close the activity and change the data, It still gets called and I can see that in logs.

So if I opened that activity say 5 times, on each update I see 5 logs messages.

Isn't there any solution where we can provide a context of that activity so the listeners die when the activity finishes.

rootRef.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.v(TAG, "Data Changed in Activity2");
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });

I need to provide too many value listeners in RecyclerView also. It also causes multiple value changed event calls after it is scrolled. Because it is bind a new liestener at the existing position, and no reference to remove from the onViewRecycled method. So they are called multiple times and changes data randomly on screen.

Having context makes it easier to set value event listeners. Is there way I can keep track of all the listeners? There is also no method to get the listeners for a reference.

kirtan403
  • 7,293
  • 6
  • 54
  • 97
  • 1
    I have the same problem and ended up keeping a list of references to listeners with their context. When context is destroyed, I iterate through and remove all listeners for it. Terrible solution but it works. – Greg Ennis Jul 12 '16 at 09:44
  • Hi Greg, can you tell how you are keeping a list of references to listeners with their context? And how removing when context is destroyed? – kirtan403 Jul 12 '16 at 09:50
  • Everywhere in the app where I add a firebase listener, I also store it in a ArrayList in an object along with the owning context/activity and the firebase query. When the activity is destroyed, I go through the list and call removeEventListener on the query. It's very fragile but I dont know what else to do. – Greg Ennis Jul 12 '16 at 12:44
  • Are you talking about this kind of a solution David mentioned [here](http://stackoverflow.com/a/33782474/1820644) ? – kirtan403 Jul 12 '16 at 12:48
  • Yes I never saw that, but that is pretty much exactly what I'm doing – Greg Ennis Jul 12 '16 at 13:00
  • 1
    Thanks! Storing and removing listeners in every activity is mess.. There should be a better way to this.. – kirtan403 Jul 12 '16 at 13:05
  • 2
    Make sure to clean up your listeners in the opposite lifecycle event of where you attach them. So if you attach the listener in `onCreate()`, remove it in `onDestroy()`. Same for `onStart()`/`onStop()` and `onResume()`/`onPause()`. – Frank van Puffelen Jul 12 '16 at 13:55
  • Hi @Frank! Providing context would be a better thing to do. Would request you to add support for it.. – kirtan403 Jul 12 '16 at 14:40
  • @FrankvanPuffelen how about providing a tag string when adding a listener, and then having ways to remove all listeners for a tag – Greg Ennis Jul 12 '16 at 15:50

1 Answers1

5

As there is currently no way to provide a context to listeners, need to keep track of all attached listeners and remove them as shown by David in the answer here: https://stackoverflow.com/a/33782474/1820644


For managing value listeners inside the RecyclerView I have came up with a solution that works well. I am using FirebaseUI's RecyclerView, but the solution should work on every RecyclerView.

Step 1: Firstly create a global Map<String,ValueEventListener> in your activity:

HashMap<String, ValueEventListener> mRecyclerViewFirebaseListeners = new HashMap<>();

Step 2: Bind ValueEventListener in populateViewHolder (in firebaseUI) or onBindViewHodler and add it to our map mRecyclerViewFirebaseListeners. Also, attach a Tag to view holder with the value of our key

protected void populateViewHolder(final ViewHolder viewHolder,
                                  Boolean model, int position) {

    Log.v(TAG, "Populating viewHolder for position: " + position);

    // Get the key and add listener
    String key = this.getRef(position).getKey();
    ValueEventListener listener = mRootRef.child("events").child(key)
            .addValueEventListener(new ValueEventListener() {
                public void onDataChange(DataSnapshot dataSnapshot) {
                    Log.v(TAG, "Event Data Change");
                    // ...
                }

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

    Log.v(TAG, "Listener set for event key: " + key + " (position: " +
            position + ")");

    // Add to our map and set tag on view holder
    mRecyclerViewFirebaseListeners.put(key, listener);
    viewHolder.itemView.setTag(R.id.TAG_RCV_EVENT_KEY, key);
    Log.d(TAG, "Added tag for position:" + position);

    // do other work here

}

Step 3: Remove a listener for the view holder when a view is recycled.

@Override
public void onViewRecycled(ViewHolder holder) {
    super.onViewRecycled(holder);

    // Get key from the view holder
    String key = (String) holder.itemView.getTag(R.id.TAG_RCV_EVENT_KEY);
    Log.v(TAG, "Recycling view set for key: " + key);

    // Remove listener for the retrieved key and also remove it from our map
    mRootRef.child("events").child(key)
            .removeEventListener(mRecyclerViewFirebaseListeners.get(key));
    mRecyclerViewFirebaseListeners.remove(key);
    Log.v(TAG, "On View Recycled, Removed Event listener for key: " + key);

    // ...

}

Step 4: Remove remaining listeners in onDestroy()

@Override
public void onDestroyView() {
    super.onDestroyView();

    // Iterate over map and remove listeners for the key
    for (Map.Entry<String, ValueEventListener> entry : mRecyclerViewFirebaseListeners.entrySet()) {
        String key = entry.getKey();
        ValueEventListener value = entry.getValue();
        mRootRef.child("events").child(key).removeEventListener(value);
        Log.v(TAG, "Removed Event listener for key: " + key);
    }
}

This will remove all your listeners from recycler view.

If you have more value listeners other than recyclerview, use a new map for those others all combine it in this map if it matches the key pattern for the same path.

Alternatively you can put a full path instead of just a key as the key of a Map. Like in example we have used value of 'key' under the child events as the key of a Map, but you can put events/<key> as the key of a Map and remove in one go in onDestroy with mRootRef.child("<your_combined_key_from_map>").removeListener(listener).

Hope this helps someone.

Community
  • 1
  • 1
kirtan403
  • 7,293
  • 6
  • 54
  • 97