Flutter firebase stream with dynamic document id/path - firebase

I have a flutter app where I use a StreamProvider in main.dart like so:
...
StreamProvider(
catchError: (context, error) {
print(error);
},
initialData: null,
create: (context) => _quizService.getCurrentQuestionStream()),
...
In the app I have a collection called QuizQuestion, each document has a date as it's id, like 2021-12-15, and so every day the app should fetch the QuizQuestion of the day. My stream function looks like this:
Stream<QuizQuestion> getCurrentQuestionStream() {
String currentDate = DateFormat('yyyy-MM-dd').format(DateTime.now());
try {
return _firebaseFirestore
.collection("QuizQuestion")
.doc(currentDate)
.snapshots()
.map((doc) => QuizQuestion.fromJson(doc.data()));
} catch (e) {
return e.message;
}
}
This works to get the current day's QuizQuestion, but if the user has the app open from one day to the next, the stream function is still "subscribed" to fetch the previous date, since it won't define the currentDate variable again. I'm trying to figure out how to solve this, is is possible to some how to listen on a day change in flutter to reinitialize the stream with a new date, or do I need to rethink the backend here?

There is no way to change an existing query. You will have to construct a new query for the new date.
There are two common ways to do this:
Track the current date in your application code, and construct the new query once you detect the new date there.
Write the current date to a fixed location/documentation in the database, e.g. qotd for question/quiz of the day. You could for example do this when you also write the quiz for the next day. Now have your application listen to that document, and when you detect a change in the document, load the quiz for that day.
Both are valid options, and you can embed either option into a stream of your own, which you then feed to the stream builder. I typically prefer the latter as it also gives me a way to control what quiz(es) can be read through security rules.

Related

How can I listen only to doc that changed in Firestore stream?

I have the following Firestore stream:
FirebaseFirestore.instance.collection("users").orderBy("timestamp", descending: true).snapshots().listen((value) async{
// here i noticed if doc was added or updated so the stream will take action to all docs
in that collection
});
For example if i write .set() to same collection like following:
.set({
name : 'Alex'
});
and I had another doc with the field name 'jack' then I used the following in the previous stream:
stream...{
if(value['name]=='Alex'){
print('ok');
}
if(value['name]=='jack'){
print('ok'); // ok here should print 'ok' if it first snapshot ,
but it will always print 'ok' whatever the new doc equal
my condition or not
}
}
How can I only listen to doc that newly added or updated or changed ?
As #Dharmaraj recommended, docChanges() method informs us of all the changes that have occurred in our database.
docChanges() method should be used on the snapshot parameter. Since that gives an array of document changes, we iterate over with a loop. If you make a console.log of the local variable of the loop, you will see that this returns each change (in the form of an object).
Example:
db.collection('recipes').onSnapshot(snapshot => {
// console.log(snapshot.docChanges());
snapshot.docChanges().forEach(change => {
console.log(change);
});
});
Do not forget that docChanges() should always be a function and never an array.
Additionally, you can add server timestamp. You can set a server timestamp in a field in your document to track when the server receives the update.
Each field receives the same server timestamp value when several timestamp fields are updated during a transaction.

Listen only to changes in document instead of all the time

I want to only listen to changes in a document in firebase, but now it seems it is listening every moment although there is no changes. I can see that because when I print it to the console it never stops.
FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.snapshots()
.listen((DocumentSnapshot documentSnapshot) {
Map firestoreInfo = documentSnapshot.data();
setState(() {
name = firestoreInfo['name'];
});
});
Could anyone help here? Also, it seems there is no "documentChanges" method for me I can call on the document snapshot data.
When you attach a listener to Firestore it initially fires with the current value of the document from the database. It then fires whenever the document changes. There is no way to skip the initial value of a listener.
A common way to get the behavior you want is to:
Store a lastUpdated timestamp field in the user document(s).
Use a query to only return documents where lastUpdated is after "now".
To perform such a query on a document with a specific ID, you'll need to use a condition on FieldPath.documentId.

increment a value in firestore with flutter

hi i am trying to increment a value when clicked the button if data is available in firestore this is my code bloc if you have any suggestion lmk please
int q = 0;
final snapShot = await Firestore.instance.collection('cart').document("LIihBLtbfuJ8Dy640DPd").get();
if(snapShot.exists){
q=q+1;
}
Firestore.instance.runTransaction((Transaction transaction) async {
await transaction.update(
Firestore.instance
.collection("cart")
.document("LIihBLtbfuJ8Dy640DPd"),
{
foodItem.name: {
'itemName': foodItem.name,
'imgUrl': foodItem.imageAssetPath,
'itemPrice': foodItem.price,
'quantity': q,
}
});
});
In November 2021, this worked for me.
FirebaseFirestore.instance.collection('users').doc(currentUser?.id).update({
'bronzeBadges': FieldValue.increment(2),
});
var quantityref = db.collection("cart").document("LIihBLtbfuJ8Dy640DPd");
// Increment the quantity field by 1.
quantityref.update({
"quantity" : firebase.firestore.FieldValue.increment(1)});
If your want to change a value based on the previous one, you have basically two approaches:
Make use of transactions. I see you're doing that but incorrectly, because you're fetching the current value outside of it, and it could change by the moment you run the update, causing data inconsistencies. I don't know about Flutter, but as far as I know, a Transaction in Firebase consists in a read operation followed by one or more write operations, and the value returned from the read will be the very last one and won't be changed before you finish the transaction, so you can be sure you're working with the latest one. I suggest you to read the Transactions docs.
increment method (recommended): See this see this answer for incrementing in Flutter
First of all, you need to get the desired document and its elements to update the document of fields. In your example, it is quantity.
First, get the document:
Firestore.instance
.collection('cart')
.document('documentID')
.get()
.then((DocumentSnapshot ds) {
// use ds, parse ds then access the quantity
});
After doing the job, you need to update the field. Thankfully, there is an updateData function in firestore instance.
db.collection('cart')
.document('documentID')
.updateData({'quantity': someQuantity});
Hope it helps.

how to get firebase time in firebase

I needed in my app firebase (server) time millisecond. because my local time will be change by mobile date time setting,
i will be see this question by not so helpful,
Flutter Firestore Server side Timestamp
var timestamp=FieldValue.serverTimestamp();
print("timestamp"+timestamp.toString());
but they print below value
====>Instance of 'FieldValue'
you should use
timestamp() {
Firestore.instance.collection('timestamp').document('time').setData({
"timestamp": FieldValue.serverTimestamp(),
});
}
Before reading the timestamp you have to set it to the server since you are generating it from there. According to this
serverTimestamp() Returns a sentinel for use with set() or update() to include a server-generated timestamp in the written data.
so first set it
Firestore.instance.collection('timestamp').document('docId').setData({'time' : FieldValue.serverTimestamp()});
then get it using basic firestore query. Finally, check out this answer on the details of the why.
For more general way of getting timestamp
getTimestamp() async{
await Firestore.instance.collection('timestamp').document('docId').setData({'time' : FieldValue.serverTimestamp()});
DocumentSnapshot doc = await Firestore.instance.collection('timestamp').document('docId').get();
Timestamp timestamp = doc['time'];
var timestampInMilliSeconds = timestamp.millisecondsSinceEpoch;
return timestampInMilliSeconds;
/* here you can call setState to your own variable before returning and keep in mind this method is async so it will return a Future. */
}

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);
});

Resources