How to update a list automatically using Firestore Stream? - firebase

In my app, I am now using a "refresh function" to update a list in Provider. When the user swipe, I call Refreshlist in my provider and with NotifyListeners() it updates my UI just fine. (The UI is linked to the list _myEleves).
I am afraid that users might use this "refresh" button too many times making unnecessary calls and "reads" on firebase and so increasing artificially the number of reads so the costs of Firebase.
Here is the code :
Future<void> refreshEleveList() async {
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail.toLowerCase())
.get();
_mesEleves = (docInfo['mesEleves'] as List<dynamic>)
.map((data) => Eleve.fromMap(data))
.toList();
notifyListeners();
}
I have been reading about STREAMS a lot, but I just can't get it right on how to start this stream, the listening to the changes on Firebase inside my PROVIDER file, so that changes will be made to "_myEleves" list.
What I want to do is that each time a change on firebase happens, it updates my list "_myEleves". Is there a simple way to do this ?
My Provider covers the whole app (I use it in the MAIN file). I thought of adding a StreamProvider, but the thing is I don't want this stream to start until user is authentified etc... and userInfo is first downloaded.
Right now : when user logs in : it downloads from firebase all necessary info, _mesEleves being one of them (This is for a teacher). Whenever a new student joins the group, it modifies firebase and so it should stream down this info into "myEleves" list on the teacher account.
Anybody can help ?

Related

Firebase firestore onUpdate trigger event not running for some documents only

For two documents, in my firestore collection, my onUpdate function will not trigger. It will for 99% of the documents in the collection, but for only two documents, it will not. Google Cloud function logs for this function don't even show a function started/stopped info entry for documents that won't trigger. Again, most docs will show the functions.logger.info() results, but some docs will not. These are documents that already exist that are not triggering onUpdate when data in the document is updated.
Firebase docs indicate that an onUpdate is "Triggered when a document already exists and has any value changed."
Has anyone else run into onUpdate() not firing only for specific document id's? I have submitted a support case to the Google Cloud trigger event team, so I'll post any updates I receive, but this is beyond odd, because 99% of my documents, which all have the same structure, trigger an onUpdate, but only specific documents will not. Very frustrating and frustrating that there are no logs of any issues at all. Something on the Google firestore side of things is blocking specific documents from running the function.
Incidentally, the document id's are the uid found in the firebase auth db for users. There seems to be a possible connection between sign in issues (firebase auth) and doc id's, which are uid's, that will not trigger via onUpdate(). However, I cannot find any docs that would support uid's, being used as doc id's, from being prevented to trigger events in firestore collections, when there are firebase auth issues for a uid. Anyway, this is my theory for the moment since there are zero logs for these documents that will not trigger an onUpdate.
Excerpt of my onUpdate function:
exports.updated = functions
.region('us-east4')
.firestore.document('myCollection/{uid}')
.onUpdate(async (data, context) => {
try {
functions.logger.info(`onUpdate triggered for uid ${context.params.uid}`, context)
const before = data.before.data()
const after = data.after.exists ? data.after.data() : null
functions.logger.info(`before data for uid: ${context.params.uid}`, before)
functions.logger.info(`after data for uid: ${context.params.uid}`, after)
} catch (error) {
functions.logger.error(error)
}
})

Firebase Realtime Database Overrides my Data at a location even when I use .push() method

Firebase Realtime Database Overrides my Data at a location even when I use .push() method. The little-concrete knowledge I have about writing to Firebase Realtime database is that writing to Firebase real time database can be done in few several ways. Two of the most prominent are the
set() and 2. push() method.
The long story short, push() is used to create a new key for a data to be written and it adds data to the node.
So fine, firebase has being co-operating with me in my previous projects but in this, I have no idea what is going on. I have tried different blends of push and set to achieve my goal but no progress so far.
In the code below, what I want to achieve is 2 things, write to a location chatUID, message and time only once, but write severally '-MqBBXPzUup7czdG2xCI' all under the same node "firebaseGeneratedId1" ->
A better structure is below.
Help with code. Thanks.
UPDATE
Here is my code
The writers reference
_listeningMsgRef = _msgDatabase
.reference()
.child('users')
.child(userId)
.child('chats')
.child(chatUIDConcat);
When a user hits sendMessage, here is the function called
void sendMessage() {
_messageController.clear();
var timeSent = DateTime.now().toString();
//Send
Map msgMap = {
'message': msg,
'sender': userId,
'time': timeSent,
'chatUID': chatUIDConcat
};
//String _key = _listeningMsgRef.push().key;
_listeningMsgRef.child(chatUIDConcat).set().whenComplete(() {
SnackBar snackBar = const SnackBar(content: Text('Message sent'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
DatabaseReference push = _listeningMsgRef.child(chatUIDConcat).push().set(msgMap);
});
}
The idea about the sendMessage function, is to write
chatUID:"L8pacdUOOohuTlifrNYC3JALQgh2+q5D38xPXVBTwmwb5Hq..."
message: "I'm coming"
newMessage: "true"
sender: "L8pacdUOOohuTlifrNYC3JALQgh2"
When it is complete, then push new nodes under the user nodes.
EDIT:
I later figured out the issue. I wasn't able to achieve my goal because I was a bit tensed while doing that project. The issue was I was wanted to write new data into the '-MqBBXPzUup7czdG2xCI' node without overwriting the old data in it.
The solution is straight forward. I just needed to ensure I wrote data in that node as new nodes under it. Nothing much, thanks
Frank van Puffelen for your assistance.
Paths in Firebase Realtime Database are automatically created when you write any data under then, and deleted when you remove the last data under them.
So you don't need to first create the node for the chat room. Instead, it gets auto-created when you write the first message into it with _listeningMsgRef.child(chatUIDConcat).push().set(msgMap)

Firestore document update is failing

i have documents in firestore which i am trying to update. These updates are successful for old users but failing for new users. I have listview where user can see all the added documents. They click on Edit and then they are navigated to a detailed document view where they can make changes and save.
Error which i am receiving
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception:
[cloud_firestore/not-found] Some requested document was not found.
I am passing document id with following from list view to single document edit view;
data: docId
In the edit page, i am loading the user id and doc id which is coming from previous page.
class Edit extends StatefulWidget {
final DataModel data;
Edit({required this.data});
String ui = FirebaseAuth.instance.currentUser!.uid;
#override
_EditState createState() => _EditState();
}
On save button i am updating firestore with following;
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: () async {
DocumentReference edit = FirebaseFirestore
.instance
.collection('edit')
.doc(ui)
.collection('edit')
.doc(widget.data.documentId);
edit.update({
});
The cloud_firestore/not-found error means the document was not found under the specified id. Please make sure the id really exists in your database.
If you are using the local emulator, make sure you are accessing the correct database.
If you are using persistent storage (offline firestore data) it might be that it still has old data or your device didn't connect to the internet and thus doesn't have the current data.
The error message is telling you that the document you build a reference to was not found for update.
There are a few ways to update data to Cloud Firestore:
update - change fields on a document that already exists.
Fails if the document does not exist.
set - overwrite the data in a document, creating the
document if it does not already exist. If you want to only partially
update a document, use SetOptions.merge(). If you want to be able to
create a document if it doesn't exist, or update it if it does
already exist, you should pass SetOptions.merge as the second
parameter, as explained by the API documentation.
So you just need to use the correct operation for your use case.
If you want to update a document without knowing if it exists or not,
you want to use a set() with the merge() option like this (this
example is for Java):
// Update one field, creating the document if it does not already exist.
Map<String, Object> data = new HashMap<>();
data.put("capital", true);
db.collection("cities").document("BJ") .set(data, SetOptions.merge());
Also one classical problem when using auth.currentUser is that it is
possible that the Auth object is not fully initialized and that
auth.currentUser.uid is therefore null. As explained in the doc you
should either use the onAuthStateChanged() observer or check that
auth.currentUser is not null.
Try :
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: ()
async {
DocumentReference edit = FirebaseFirestore .instance .collection('edit') .doc(FirebaseAuth.instance.currentUser.uid) .collection('edit') .doc(widget.data.documentId);
edit.update({ });

How to ensure the FieldValue.serverTimestamp() finish executing in Cloud Firestore before I get the latest data on my listener?

I'm coding in Flutter and I'm updating a document in my Cloud Firestore
// Prepare the data
final Map<String, dynamic> data = {
'name': newName,
'lastUpdate': FieldValue.serverTimestamp()
};
// Update the data in Firestore
await documentReference.updateData(data);
I also have a subscription that listens to that documentReference like this:
subscription = documentReference
.snapshots().listen((){});
subscription.onData(printSnapshotData);
What happen is I only update the data once but I will get two data reads on my listener.
The first one is like this:
name: 'newName'
lastUpdated: null
The second one is like this:
name: 'newName'
lastUpdated: Timestamp(seconds=1594368407, nanoseconds=376000000)
So it seems that there was a delay between the first one and the second one and the app gets the data twice.
What I want is to get the data after the Timestamp in the lastUpdated field is done. Only read once is enough because the first data is useless for me. How to do that ?
Thank you!
The fact that you are getting two updates on your listener is expected, and that behavior can't be changed. Listeners always fire immediately with local changes, then again every time the data has changed. Since the SDK doesn't know the final value of the timestamp before it's written, it will invoke once without the final value, then again with the final server timestamp. The first callback doesn't cost any document reads - it is working purely with local data.
If you want to know if the snapshot delivered to your callback comes from the server, you will have to check the data in the snapshot to see if it contains what you need.
For more detailed information about how server timestamps work, read this blog post.
Further to Doug extremely informative answer. I wanted to add that it is possible to use the snapshots metadata's hasPendingWrites property to filter out the local updates before the timestamp is added. See example below.
subscription = documentReference
.snapshots()
.where((snapshot) => !snapshot.metadata.hasPendingWrites)
.listen((snapshot){
// Your code here
});

Why does querying Firebase realtime db require listener?

I am new to firebase and noSQL databases. I've read the docs and watched instructional videos but there is one concept I haven't fully grasped.
Why is it that querying the database requires a listener such as .on("value")?
Since these listeners are triggered whenever there is a change of sorts (nodes created, edited, children created) shouldn't there be a more direct way of getting the data from the db? Such as
ref.orderBy("age"). equalTo(30).get()
A method to just get what's in there at the time he instruction is executed, without having to listen to some sort of event?
In SQL it's not like you have to wait for something to change in your db to make this query work:
SELECT * FROM TABLE WHERE X == Y
PS: I know .once() also exists, but my question is more about: if my db never changed, how would I be able to query it and always get the same query result/snapshot?
You didn't define a platform so I will use this Swift pseudo-code example. Suppose we want to get a list of all users, one time.
let usersRef = my_firebase.child("users")
usersRef.observeSingleEvent(by: .value, with: { snapshot in
//all users are returned to the app in the snapshot
// .value means 'give me everything in the specified node'
// so then we can iterate over the snapshot to get each users data
}
You can call the above code any time to get the list of users and it does not add an observer (listener) to the node. It's a one-shot.
Whereas, if we want to be notified of users that are added
let usersRef = my_firebase.child("users")
usersRef.observe(by: .childAdded, with: { snapshot in
//upon first calling this, each user will be sent to the app in the snapshot
// and after that, any user that's added
}
The above code attaches an observer (listener) to the users node and whenever a user is added, it's provided to the app via the snapshot.
Note the use of .observeSingleEvent vs .observe and .value to get everything in the node vs .childAdded to get a specific node.

Resources