Reputation: 2468
I have set two listeners
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
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
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
Reputation: 131
There are 2 scenarios where this may happen:
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.
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:
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
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
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