rookieDeveloper
rookieDeveloper

Reputation: 2468

firebase on Data Change is called multiple times in add value event listener

I have set two listeners

  1. Query to get the last message
  2. If the last message is group type I'm getting group info using group Listener

However, when I get the type from the message using messageListener and set groupListener it returns multiple onDataChange Debug.e("parentsnap",dataSnapshot.getValue().toString()); gets called more than one times How should I do it, please guide

    groupListener = new ValueEventListener() {
        @SuppressWarnings("unchecked")
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            Debug.e("parent snap", dataSnapshot.getValue().toString());
            for (DataSnapshot d :
                    dataSnapshot.getChildren()) {
           //my code
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };
    groups.child(chatId).addValueEventListener(groupListener);

Upvotes: 5

Views: 13131

Answers (5)

Andy H.
Andy H.

Reputation: 465

I faced the same problem when using a FirestoreRecyclerAdapter to automatically refresh Firestore data in a RecyclerView in Android.

I just wanted to emit a Boolean each time the data changed, to show or hide an empty data message in the view ("No data found; try changing your filter.") by means of a LiveData which is backed by an RX subject.

The problem is that onDataChanged() was being invoked twice after every query change, so I needed a way to coalesce these 2 values into a single value, and only use the latest of the two values.

I already had a LiveData backed by an RX observable, so simply adding a buffer(2) and a map() made it easy to coalesce.

class MyViewModel : AndroidViewModel() {

    /**
     * Indicates whether or not an empty state message should be displayed.
     */
    val noMatchingDataLiveData: LiveData<Boolean> by lazy {
        noMatchingDataSubject
            // Ignore the first value
            .skip(1)

            // Only emit after every 2 items, since each data change from the adapter emits 2 messages
            .buffer(2)

            // Only emit the most recent of the 2 items
            .map { it.last() }

            .toFlowable(BackpressureStrategy.LATEST)

            // Transforms an RX observable to a LiveData via LiveDataReactiveStreamsKt
            .toLiveData()
    }

    /**
     * The backing subject for [noMatchingDataLiveData].
     */
    private val noMatchingDataSubject = BehaviorSubject.createDefault<Boolean>(false)

    /**
     * The recycler view adapter.
     */
    internal val myRecyclerAdapter: FirestoreRecyclerAdapter<MyWidget, MyWidgetViewHolder> by lazy {
        object : FirestoreRecyclerAdapter<MyWidget, MyWidgetViewHolder>(...) {

            override fun onCreateViewHolder(group: ViewGroup, i: Int): MyWidgetViewHolder {
                return MyWidgetViewHolder(LayoutInflater.from(group.context).inflate(R.layout.my_widget_item, group, false))
            }

            override fun onBindViewHolder(
                holder: MyWidgetViewHolder,
                position: Int,
                model: MyWidget) {
                ...
            }

            override fun onDataChanged() {
                // Note that each time the query is changed, this is invoked twice:
                // The first invocation always has 0 items, because the offline cache was just cleared
                // The second invocation will have the number of items matching the query, when the offline cache is updated

                // Our subject uses a buffer to only emit every-other item
                noMatchingDataSubject.onNext(this.itemCount < 1)
            }

            override fun onError(e: FirebaseFirestoreException) {
                ...
            }
        }
    }
}

Upvotes: 1

DragonFire
DragonFire

Reputation: 4102

This removing of listener need not be only on activity/fragment life cycle, it must be handled even if you are on the same activity/fragment and call the addValueEventListener again on click of a button. If the listener is already active and you start another one both the listeners are active and you get duplicate/multiple results. It took me some time to understand this basic concept. Here is how I do it..

Declare Your Variables

private ValueEventListener getGroupListListener;
private Query getGroupListQuery;

Call Your Function

getGroupListFromFireBase("0");

Define Your Function

private void getGroupListFromFireBase(final String hiddenStatus) {

    removeListeners();

    getGroupListQuery = fbDbRefRoot.child("groups")
                                   .child(fUserId)
                                   .orderByChild("hidden")
                                   .equalTo(hiddenStatus);

    getGroupListListener = getGroupListQuery.addValueEventListener(new ValueEventListener() {

        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

            if (dataSnapshot.exists()) {

                lGroupsList.clear();
                // Do Stuff Here

            }

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

}

Remove Listener Function

// Remove The Listeners
private void removeListeners() {

    lGroupsList.clear();

    // Remove Listeners
    if (getGroupListListener != null & getGroupListQuery != null) {
        getGroupListQuery.removeEventListener(getGroupListListener);

    }

}

Reattach Listeners Function

// Re Attach The Listeners
private void reAttachListeners() {

    // Re Attach The Listeners
    if (getGroupListListener != null & getGroupListQuery != null) {
        getGroupListQuery.addValueEventListener(getGroupListListener);

        rvGroupList.setAdapter(aGroupList);

    }

}

I have given the Reattach Listener function just in case you want to use these in onPause (Remove Listeners) and onResume (ReAttach Listeners) - onPause and onResume work in both activities and fragments.

Upvotes: 1

Ajitesh Shukla
Ajitesh Shukla

Reputation: 131

There are 2 scenarios where this may happen:

  1. onDataChange is called twice in case you have enabled offline persistence. Once with the stale offline value and again with the updated value in case it has changed.

  2. onDataChange is called multiple times in case you have not removed the listener properly and are creating a new instance of your listener in your activity every time you open it.

Scenario 2 is easy to fix. You can maintain local references of your firebase reference and listener, than you can do a ref.removeListener(listener) in onDestroy of your Activity. Scenario 2 is difficult to fix and you have 2 possible remedies:

  1. Disable offline persistence in case you always want the updated latest value.
  2. Do a runnable.postDelayed(callbackRunnable, 3000); to wait for the latest value for 3 seconds before updating the views or whatever you want to update.

Upvotes: 0

drod
drod

Reputation: 369

Check if you have

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

and now test without it.

If you are persisting data locally and using addListenerForSingleValueEvent(...), it can cause multiple(2) calls to get fresh data.

Upvotes: 4

Linh
Linh

Reputation: 61019

If you need to receive data one time you need to use addListenerForSingleValueEvent(...) instead of addValueEventListener(...). Then onDataChange() will return only one time

Upvotes: 9

Related Questions