Firestore: How to listen for changes to a document that doesn't exist yet? - flutter

I have two apps that access the same firestore database. The 'User' app listens for changes to a document in a collection that doesn't exist until the 'Admin' app creates it. Here is the code:
Stream<DocumentSnapshot>logStream = FirebaseFirestore.instance
.collection('companies')
.doc(companyId)
.collection('log')
.doc(DateFormat('yyyy-MM-dd').format(DateTime.now()))
.snapshots();
}
The problem is that I cannot start listening to 'logStream' unless it exists.
Is there a way to check when it's created and then start listening?

You can't start listening to stream immediatly after the document is created, that is not something that Firestore supports natively. So in order to do this you are going to have make a create a function that is called sporadically to check this until it is created, this function will make a get(), check if the document exists and if positive start listening to it.
You can use StreamCompleter, as mentioned in the comments, to listen to an "empty" stream before that without triggering any actions, but the calls to check existence of the document mentioned earlier will still need to be made, so you code can look like this:
var db = FirebaseFirestore.instance;
var completer = StreamCompleter<DocumentSnapshot>();
Stream<DocumentSnapshot>logStream = completer.stream();
// this function will need to be called sporadically
checkDocumentExists(completer);
checkDocumentExists(completer){
var reference = db.collection('companies')
.doc(companyId)
.collection('log')
.doc(DateFormat('yyyy-MM-dd').format(DateTime.now()));
reference.get() => then(function(document) {
if(document.exists){
completer.setSourceStream(reference.snapshots());
}
}
}
If you can't progress with your app execution until the document exists you can create an keep calling checkDocumentExists until the stream is ready.

Related

Vue3, firestore, useFirestore: How to get notified when binding is ready?

I'm currently migrating my project from Vue2+Vuex to Vue3+pinia. The storage of choice is still firebase. To keep my local storage in sync with the remote firebase-database I want to setup a (one-way) binding. I plan to use useFirestore as documented here.
I wrapped in a pinia action:
async bindTodos () {
this.todos = todos = useFirestore(collection(db, 'todos'));
}
with this.todos being defined in pinias state.
How can I maintain a boolean in the pinia state (i.e. todosReady) that get's updated when the todos were received the first time. Vuex/bindFirestoreRef used to return a promise. Or I am supposed to use import {watch} from "vue" and simply watch for changes in the returned Ref? Hope I'm on the right track!

How to read and listen for changes in firebase realtime database

I am trying to listen for changes inside an Object in firebase realtime database. When I run the code, it prints the values right and once, but when I try to change, add or remove a value inside this Object, the listen function seems to be triggered more than once. When I try to monitor what is happening in firebase, it looks like when I try to add a value, it creates multiple values at first, then removes all of them but one. This seems to be the problem, Does someone know what can be done?
previousReadingsRef.onValue.listen((event) {
previousReadings = event.snapshot.children
.where((element) => element.key.toString().contains("2022"));
for (var x in previousReadings) {
print(x.key);
}
});

Await reading document and then setup listener?

Using Flutter and Firestore I frequently setup this block of code.
DocumentReference reference = Firestore.instance.collection('movies').document(uid);
reference.snapshots().listen((documentSnapshot) {
<respond to updates>
});
This fires the listen for the initial read and any changes.
However sometimes I want to do an await for the first read and then setup the listener for changes. How can I do that? Specifically how do I await for the first read, and second make the listen not fire unless there are any changes?

Async/Await/then in Dart/Flutter

I have a flutter application where I am using the SQFLITE plugin to fetch data from SQLite DB. Here I am facing a weird problem. As per my understanding, we use either async/await or then() function for async programming.
Here I have a db.query() method which is conducting some SQL queries to fetch data from the DB. After this function fetches the data, we do some further processing in the .then() function. However, in this approach, I was facing some issues. From where I am calling this getExpensesByFundId(int fundId)function, it doesn't seem to fetch the data properly. It's supposed to return Future> object which will be then converted to List when the data is available. But when I call it doesn't work.
However, I just did some experimentation with it and added "await" keyword in front of the db.query() function and somehow it just started to work fine. Can you explain why adding the await keyword is solving this issue? I thought when using .then() function, we don't need to use the await keyword.
Here are my codes:
Future<List<Expense>> getExpensesByFundId(int fundId) async {
Database db = await database;
List<Expense> expenseList = List();
// The await in the below line is what I'm talking about
await db.query(expTable,where: '$expTable.$expFundId = $fundId')
.then((List<Map<String,dynamic>> expList){
expList.forEach((Map<String, dynamic> expMap){
expenseList.add(Expense.fromMap(expMap));
});
});
return expenseList;
}
In simple words:
await is meant to interrupt the process flow until the async method has finished.
then however does not interrupt the process flow (meaning the next instructions will be executed) but enables you to run code when the async method is finished.
In your example, you cannot achieve what you want when you use then because the code is not 'waiting' and the return statement is processed and thus returns an empty list.
When you add the await, you explicitly say: 'don't go further until my Future method is completed (namely the then part).
You could write your code as follows to achieve the same result using only await:
Future<List<Expense>> getExpensesByFundId(int fundId) async {
Database db = await database;
List<Expense> expenseList = List();
List<Map<String,dynamic>> expList = await db.query(expTable,where: '$expTable.$expFundId = $fundId');
expList.forEach((Map<String, dynamic> expMap) {
expenseList.add(Expense.fromMap(expMap));
});
return expenseList;
}
You could also choose to use only the then part, but you need to ensure that you call getExpensesByFundId properly afterwards:
Future<List<Expense>> getExpensesByFundId(int fundId) async {
Database db = await database;
List<Expense> expenseList = List();
return db.query(expTable,where: '$expTable.$expFundId = $fundId')
.then((List<Map<String,dynamic>> expList){
expList.forEach((Map<String, dynamic> expMap){
expenseList.add(Expense.fromMap(expMap));
});
});
}
// call either with an await
List<Expense> list = await getExpensesByFundId(1);
// or with a then (knowing that this will not interrupt the process flow and process the next instruction
getExpensesByFundId(1).then((List<Expense> l) { /*...*/ });
Adding to the above answers.
Flutter Application is said to be a step by step execution of code, but it's not like that.
There are a lot of events going to be triggered in the lifecycle of applications like Click Event, Timers, and all. There must be some code that should be running in the background thread.
How background work execute:
So there are two Queues
Microtask Queue
Event Queue
Microtask Queue runs the code which not supposed to be run by any event(click, timer, etc). It can contain both sync and async work.
Event Queue runs when any external click event occurs in the application like Click event, then that block execution done inside the event loop.
The below diagram will explain in detail how execution will proceed.
Note: At any given point of application development Microtask queue will run then only Event Queue will be able to run.
When making class use async for using await its simple logic to make a wait state in your function until your data is retrieve to show.
Example: 1) Its like when you follow click button 2) Data first store in database than Future function use to retrieve data 3) Move that data into variable and than show in screen 4) Variable show like increment in your following/profile.
And then is use one by one step of code, store data in variable and then move to next.
Example: If I click in follow button until data store in variable it continuously retrieve some data to store and not allow next function to run, and if one task is complete than move to another.
Same as your question i was also doing experiment in social media flutter app and this is my understanding. I hope this would help.
A Flutter question from an answer from your answer.
await is meant to interrupt the process flow until the async method has finished. then however does not interrupt the process flow but enables you to run code when the async method is finished. So, I am asking diff. between top down & bottom down process in programming.

Standard practice error handling for partial data uploads in Firestore?

Would like to know what (if any) the standard / best-practice way is for handling failed / partial data uploads to firestore DB.
For example, suppose a user needs to upload to two different collections via something like
let res = undefined
let docID = firebase.auth().currentUser.uid
try {
res = await firebase.firestore().collection('someCollection')
.doc(docID)
.set(someData)
} catch (error) { console.error(`Some message ${res}`) }
try {
res = await firebase.firestore().collection('someOtherCollection')
.doc(docID)
.set(someOtherData)
} catch (error) { console.error(`Some message ${res}`) }
and that for whatever reason, the program successfully writes to someCollection, but not to someOtherCollection. How should this be handled? Should we attempt to roll back what has been written (via the catch blocks) or wait until the end of upload attempts and add / upload an additional flag to the document and only treat data as valid in the future if it has this flag? Something else?
** Realize that this is close to some Questions to Avoid Asking, but in this case am asking specifically for Firestore API (eg. any functions / objects within the firestore() API that is specifically intended to handel these cases) and best practices (if there are any in the wider world (pretty new to using Firebase / Firestore)).
You should use a Firestore transaction to ensure that all the documents have updated atomically. This will ensure that all of the documents are updated, or none of them.
From the referenced docs:
Cloud Firestore supports atomic operations for reading and writing data. In a set of atomic operations, either all of the operations succeed, or none of them are applied.