Flutter Firebase RTDB onValue triggered multiple times - firebase

I'm developing an app which needs live data from the database which was created using Firebase RTDB. I used onValue to get the live data, the problem is that the onValue is triggered multiple times when a lot of data changed at once and messed up my data processing system. Been trying to look for solutions, but still haven't managed to find one. Any help will be appreciated. Here's the code:
await refConnection
.child(user!.uid).orderByChild('date_created').onValue.forEach((element) {
if (element.snapshot.value != null) {
Map<dynamic, dynamic> connection = element.snapshot.value as Map<
dynamic,
dynamic>;
print("Test: $connection");
connection.forEach((key, value) {
if (connection[key]['status'] == 'connected') {
Provider.of<ContactDataProvider>(context, listen: false)
.addConnected(connection[key]);
} else if (connection[key]['status'] == 'incoming_req') {
Provider.of<ContactDataProvider>(context, listen: false)
.addIncoming(connection[key]);
} else if (connection[key]['status'] == 'sent_req') {
print(connection[key]);
Provider.of<ContactDataProvider>(context, listen: false)
.addSent(connection[key]);
}
});
setState();
}
});

Do you really need to listen for live updates? If not, then try getting the value only once using get().
Solution: Trying with a different approach

Can you please provide an example about your user child structure?
As I can determine by your code, do you have multiple items with different data inside your user child, each data with a field called status?
If that is correct, then you should use onChildChanged listener to list only when a child inside userId is changed.
That will be triggered when any child inside userId change its value, then you can verify if the childChanged.key == status then do your logic
But keep aware of the fact that any value changed inside userId child, will trigger your listener.
avoid cyclic calls

Related

Firebase Listener Called Multiple Times When Data Change SwiftUI

I have three listeners
Get Visible Data to all user
Get Data Between two dates
Get All Data
However, When any Data change user table the Listener call automatically 4-5 times.
func listenData() -> ListenerRegistration {
let listener = db.collection("user")
.whereField("FirstRow", isGreaterThanOrEqualTo: "FirstRow")
.whereField("lastRow", isLessThanOrEqualTo: "lastRow")
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("listener error: \(error.localizedDescription)")
return
}
if let snapshot = querySnapshot {
print("Without For Each = Data")
snapshot.documentChanges.forEach { diff in
print("For Each = Data")
}
print("listen Public Rides Loop Done")
}
}
return listener
}
Question: How to Listener call once time when user change the data?
Can someone please explain to me How to Listener call once time only?
Any help would be greatly appreciated.
Thanks in advance.
Firestore's onSnapshot will always also give you the initial snapshot of the data in the database. There is no way to tell the API to skip this initial snapshot.
If you don't need the initial data for your use-case, you will either have to ignore it in your application code, or you will have to come up with a query that only returns the documents you're interested in. That last one has been covered a few times before, so I recommend looking at the answers to these questions.
If you want to stop listening for more updates after the first change, you can detach the listener at that point.

Trying to filter down chat messages using Flutter and Firestore

I'm trying to create a Blocked Users list in my chat app using the latest versions of Flutter Beta channel (1.23.0-18.1.pre) and cloud_firestore 0.14.3.
Here's my data structure:
At first, I tried something like this (Hardcoded just to test), by filtering the messages I'm querying from Firestore. Firebase doesn't like this.
query: firestore.collection('messages')
.where('userId', whereNotIn: ['123456789', '987654321'] )
.where('hashtag', isEqualTo: hashTag)
.orderBy('submittedAt', descending: true),
reverse: true,
I get this error:
E/FLTFirestoreMsgCodec(24331): java.lang.IllegalArgumentException: Invalid query. You have an inequality where filter (whereLessThan(), whereGreaterThan(), etc.) on field 'userId' and so you must also have 'userId' as your first orderBy() field, but your first orderBy() is currently on field 'submittedAt' instead.
After doing some more reading, filtering on the client-side by just hiding the messages actually better suits my needs.
Unfortunately, I'm running in circles. I'm currently thinking I would map a stream to a list, and then do something like this:
if (message.userId is in the list) {
isBlocked = true;
} else {
isBlocked = false;
}
And then filtering out the messages if isBlocked is true. I tried hardcoding the values for that and it worked. BTW, Sorry for the pseudocode, but I deviated so many times that now I'm simply lost.
I was wondering if this was the correct approach? Any suggestions would be rad. I also tried using a future list from a stream but I couldn't get that to work either.
Future<Stream<List<BlockedUser>>> getBlockedIds() async {
Stream<List<BlockedUser>> list;
Stream<QuerySnapshot> snapshot = FirebaseFirestore.instance.collection('user').doc('id').collection('blocked').snapshots();
list = snapshot.map((query) => query.docs.map(
(doc) => BlockedUser(
id: doc.data()['id'])
).toList());
return list;
}
I can't get that to work since I don't know what to do with that list.
Thanks, everyone!

Is it possible to go through documents in cloud firestore to see if a value of a property is equal to a comparing one?

I have website written in plain javascript to keep daily to-do tasks and the app crashed lately because different tasks of the same date was created on accident. My question is...
how can i write an if statement that checks if a document from a collection has a property (in my case the date) that is equal to the one in the input field of my form. i guess it should check after i click submit? if it exists, creation should be denyed, if not, ok to proceed.
i am using cloud firestore by the way... many thanks in advance for the help!
First, make a query to get a document that has same date:
var query = db.collection("yourCollectionName").where("date", "==", dateInInputfield);
query.get().then(function(querySnapshot) {
if (querySnapshot.empty) {
//empty
} else {
// not empty
}
});
If empty{you can proceed}, if notEmpty{some other task already exist on same date}
If you are making an app like this, a cleaner approach will be to name the id of a document as it's date, for eg. if a task is created at timestamp of 1234567, create a document named 1234567 and inside it, store all the necessary information.
By following this approach, if you create a new task, simply fetch a document by the name in inputfield,
var docRef = db.collection("yourCollectionName").doc("date");
docRef.get().then(function(doc) {
if (doc.exists) {
//this means some other document already exists
} else {
//safe to create a new document by this date.
}
}).catch(function(error) {
console.log("Error:", error);
});

Good way to delete all data according to criteria/child's value in Firebase database admin?

I want to clean up this userPublic by deleting all of its child node which has isTesting == true. I am using Firebase's cloud function. My approach would be :
const userPublic = admin.database().ref("/userPublic")
const testsInUserPublic = userPublic.orderByChild("isTesting").equalTo(true)
testsInUserPublic.once("value", dataSnapshot => {
// ???
})
Since I can only call .remove() on reference and not snapshot but to filter the child I want it returns snapshot, how can I get the reference from snapshot? (I would like to know the key XXX-XXX-XXX of each filtered child, so I can concatenate with userPublic and .remove() them one by one)
Also, even if I can get all the references that I want to remove I think deleting them one by one by calling .remove() then wait for promise, then call the next one does not sounds like an optimal way. Are there any way to remove all of them in one go?
If it involves calling .update() on the top userPublic node, I would have to fetch everything, remove the one with isTesting and then put the remaining back for update. This sounds like it is not efficient compared to the filtering way. As eventually the one with .isTesting is only about 5% of all data. Or is this actually the approach everyone is using?
You're almost there. All that's left is to create a single multi-location update from the results of your query:
const userPublic = admin.database().ref("/userPublic")
const testsInUserPublic = userPublic.orderByChild("isTesting").equalTo(true)
testsInUserPublic.once("value", snapshot => {
var updates = {};
snapshot.forEach(function(child) {
updates["/userPublic/"+child.key] = null;
});
userPublic.update(updates);
})
Doing this with promises would not be too different:
testsInUserPublic.once("value", snapshot => {
var promises = [];
snapshot.forEach(function(child) {
promises.push(child.ref.remove());
});
Promise.all(promises); // this returns a promise that resolves once all deletes are done, or that rejects once one of them fails
})
Performance of this will be very similar, since Firebase pipelines the requests over a single connection. See http://stackoverflow.com/questions/35931526/speed-up-fetching-posts-for-my-social-network-app-by-using-query-instead-of-obse/35932786#35932786

How to store users and groups for a chat using Firebase

I would like to make a chat using Firebase. I need to display for each users the list of group they belong to and also for each group all the members.
Because of how Firebase is designed, and in order to have acceptable performance, I am think making a list of all the groups containing the list of members, and for each users, an entry with all the group they belong too.
My first question is, is it the right approach?
If so, my second question is how can I atomically add (or removed) a user, i.e. making sure both the user is added in the group and the group added into the user or not added at all (i.e. never stored at 1 location only and make the DB inconsistent) ?
The data model you have proposed is consistent with recommendations for firebase. A more detailed explanation is outlined in best way to structure data in firebase
users/userid/groups
groups/groupid/users
Firebase provides .transaction for atomic operations. You may chain multiple transactions to ensure data consistency. You may also use onComplete callback function. For detailed explanation refer to firebase transaction. I am using this same approach to keep multiple message counts up to date for a dashboard display.
Sample code for nested transaction:
ref.child('users').child(userid).child(groupid).transaction(function (currentData) {
if (currentData === null) {
return groupid;
} else {
console.log('groupid already exists.');
return; // Abort the transaction.
}
}, function (error, committed, snapshot) {
if (committed) {
console.log('start a new transaction');
ref.child('groups').child(groupid).child(userid).tranaction(function (currentData) {
if (currentData === null) {
return userid;
} else {
console.log('Userid already exists.');
//add code here to roll back the groupid added to users
return; // Abort the transaction.
}
}, function (error, committed, snapshot) {
if (error) {
//rollback the groupid that was added to users in previous transaction
}
})
}
});

Resources