How can i make transection using .where - firebase

Since transaction only accepts DocumentReference so I couldn't handle the .where().
DocumentReference test1= FirebaseFirestore.instance.collection("users").doc(currentUser.uid);
Query<Map<String, dynamic>> test2= FirebaseFirestore.instance.collection("users").where('test',isEqualTo: 'mytest').limit(1);
return FirebaseFirestore.instance.runTransaction((transaction) async {
//no problem with this DocumentSnapshot
DocumentSnapshot _value = await transaction.get(test1);
//BUT
//here in test2 will never accept Query<Map<String, dynamic>>
//since the transaction is only accept DocumentReference
DocumentSnapshot _value2 = await transaction.get(test2);
if (_value.exists) {
//action
}
})
How can I get the .whereReference into the transaction?

Cloud Firestore doesn't provide a solution in which you can use transactions on documents that are obtained through queries. Please also note that a Transaction object cannot perform a query. The single option that you have is to use transactions on a single individual document.
According to your last comment, since you are using a .limit(1) it means that you need to update a single document, a case in which you can create a reference that points exactly to that document and perform the transaction:
DocumentReference uidRef = FirebaseFirestore.instance.collection("users").doc(currentUser.uid);
uidRef.runTransaction(/* ... /*);
If you don't know the UID of the user on which you need to perform the transaction, then you should perform the necessary query outside the transaction, get the ID of the document, and perform the transaction right after that. But as you can see, there is no way you can do these two operations in a single go.
Please also remember, that a transaction makes sense to be used only if you need to read some data from a document in order to make a decision before you update it. I'm not 100% sure, but in your particular case, I don't think you do that. You are not using the value of _value in a way you decide what to do with it. If this is your scenario, a batch write will definitely do the job.

Related

Flutter Firestore - How to get data from a Document Reference in a Document Field?

I'm building a Self-learning app with differente questions types. Right now, one of the questions have a field containing a list of DocumentReferences:
In Flutter, I have the following code:
Query<Map<String, dynamic>> questionsRef = firestore
.collection('questions')
.where('lesson_id', isEqualTo: lessonId);
await questionsRef.get().then((snapshot) {
snapshot.docs.forEach((document) {
var questionTemp;
switch (document.data()['question_type']) {
....
case 'cards':
questionTemp = CardsQuestionModel.fromJson(document.data());
break;
....
}
questionTemp.id = document.id;
questions.add(questionTemp);
});
});
Now, with "questionTemp" I can access all the fields (lesson_id,options,question_type, etc..), but when it comes to the "cards" field, how Can I access the data from that document reference?
Is there a way to tell firestore.instance to get the data from those references automatically? Or do I need to make a new call for each one? and, if so, how can I do that?
Thank you for your support in advance!
Is there a way to tell firestore.instance to get the data from those
references automatically? Or do I need to make a new call for each
one?
No there isn't any way to get these documents automatically. You need to build, for each array element, the corresponding DocumentReference and fetch the document.
To build the reference, use the doc() method
DocumentReference docRef = FirebaseFirestore.instance.doc("cards/WzU...");
and then use the get() method on this DocumentReference.
docRef
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document exists on the database');
}
});
Concretely, you can loop over the cards Array and pass all the Futures returned by the get() method to the wait() method which "waits for multiple futures to complete and collects their results". See this SO answer for more details and also note that "the value of the returned future will be a list of all the values that were produced in the order that the futures are provided by iterating futures."

Fetch collection startAfter documentID

Is there a way to fetch document after documentID like
private fun fetchCollectoionnAfterDocumentID(limit :Long){
val db = FirebaseFirestore.getInstance()
var query:Query = db.collection("questionCollection")
.startAfter("cDxXGLHlP56xnAp4RmE5") //
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
query.get().addOnSuccessListener {
var questions = it.toObjects(QuestionBO::class.java)
questions.size
}
}
I want to fetch sorted questions after a given Document ID. I know I can do it using DocumentSnapShot. In order to fetch the second time or after the app is resume I have to save this DocumentSnapshot in Preference.
Can It be possible to fetch after document ID?
startAfter - > cDxXGLHlP56xnAp4RmE5
Edit
I know I can do it using lastVisible DocumentSnapshot . But I have to save lastVisible DocumentSnapshot in sharedPreference.
When app launch first time 10 question are fetched from questionCollection. Next time 10 more question have to be fetched after those lastVisible. So for fetching next 10 I have to save DocumentSnapshot object in sharedPreference. Suggest me a better approach after seeing my database structure.
And one more thing questionID is same as Document reference ID.
There is no way you can pass only the document id to the startAfter() method and simply start from that particular id, you should pass a DocumentSnapshots object, as explained in the official documentation regarding Firestore pagination:
Use the last document in a batch as the start of a cursor for the next batch.
first.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
=// Get the last visible document
DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
// Construct a new query starting at this document,
Query next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible) //Pass the DocumentSnapshot object
.limit(25);
// Use the query for pagination
}
});
See, here the lastVisible is a DocumentSnapshot object which represents the last visible object. You cannot pass only a document id. For more information, you can check my answer from the following post:
How to paginate Firestore with Android?
It's in Java but I'm confident you can understand it and write it in Kotlin.
Edit:
Please consider defining an order of your results so that all your pages of data can exist in a predictable way. So you need to either specify a startAt()/startAfter() value to indicate where in the ordering to begin receiving ordered documents or use a DocumentSnapshot to indicate the next document to receive, as explained above.
Another solution might be to put the document id into the document itself (as a value of a property) and order on it, or you can use FieldPath.documentId() to order by the id without having to add one.
You can also check this and this out.
There is one way to let startAfter(documentID) works.
Making one more document "get", then using the result as startAfter input.
val db = FirebaseFirestore.getInstance()
// I use javascript await / async here
val afterDoc = await db.collection("questionCollection").doc("cDxXGLHlP56xnAp4RmE5").get();
var query:Query = db.collection("questionCollection")
.startAfter(afterDoc)
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
A simple way to think of this: if you order on questionID you'll need to know at least the value of questionID of the document to start after. You'll often also want to know the key, to disambiguate between documents with the same values. But since it sounds like your questionID values are unique within this collection, that might not be needed here.
But just knowing the key isn't enough, as that would require Firestore to scan its entire index to find that document. Such an index scan would break the performance guarantees of Firestore, which is why it requires you to give you the information it needs to perform a direct lookup in the index.

how do I check collection exists or not in firestore on flutter?

I get some data from firestore. Sometimes when I called to get data, the collection is not created yet.
before calling get request, how do I check collection is exists or not?
Stream<List<ChatModel>> getChat(ChatFieldModel model) {
var ref = _db.collection('chats');
return ref
.document(model.docId)
.collection('messages')
.orderBy('timestamp', descending: true)
.snapshots()
.map((list) =>
list.documents.map((doc)=>ChatModel.fromForestore(doc)).toList());
}
I posted this before
final snapshot = await firestore.collection(roomName).getDocuments();
if (snapshot.documents.length == 0) {
//doesnt exist
}
Hope this helps
Collections are not created or deleted independently of documents. When you create a document and specify it as being part of a certain collection, that collection is created automatically if it does not already exist. Similarly, when you remove the last document in a collection, that collection will automatically be deleted. So there is really no circumstance where you need to worry about whether a collection has been created or not, and you have no explicit control over creating or deleting collections.
Usually a collection in Firestore gets deleted if no Documents exist in it.
However, there might be cases where you want to keep a history of the collection modification events, or let's say for some reason prevent Collections from being deleted.
Or for example, you want to know when a collection was created in the first place. Normally, if the Documents are deleted, and then the Collection gets created again, you will not know the initial creation date.
A workaround I can think of is the following:
Initialize each collection you want with a Document that will be specifically for keeping generic info about that collection.
For example:
This way, even if all other Documents in the Collection are deleted, you'll still keep the Collection in addition to some info that might be handy if In the future you need to get some history info about the Collection.
So to know if a Collection exists of no, you can run a query that checks for a field in Info Documents (eg CollectionInfo.exists) to know which Collections have been already created.
This is for the most recent update
final snapshot = await FirebaseFirestore.instance
.collection('collection name').get();
if ( snapshot.size == 0 ) {
print('it does not exist');
}
Feb 2022
Get QuerySnapshot and return querySnapshot.docs.isNotEmpty or isEmpty
Future<bool> isItems() async {
CollectionReference collectionReference =
_firestore.collection("users").doc(_user!.uid).collection("cart");
QuerySnapshot querySnapshot = await collectionReference.get();
return querySnapshot.docs.isNotEmpty;
}
await FirebaseFirestore.instance
.collection('collectionName')
.limit(1)
.get()
.then((snapshot) {
if (snapshot.size == 0) {
print("No collection");
}
});

Async loading two dependent streams when using FutureBuilder

In a pretty complex sheet, I am using FutureBuilder and Listview.builder which help me to list all groups around me (using Algolia search), which looks as follows:
future = _algolia
.index('groups')
.setAroundLatLng(
'${_startLocation.latitude.toString()}, ${_startLocation.longitude.toString()}')
.setFacetFilter("category:$category")
.search(_query)
.setHitsPerPage(1000)
.getObjects();
Now, I also want to include the totalUsers for each group. Problem: they are saved in Firestore (groups/groupId/totalUsers). Now for Firestore when I need to request the totalUsers, I have to hand over every groupId (which comes async from Algolia and is simply available in FutureBuilder as document.data['groupId']). So I tried to call totalUsers from a ListTile within FutureBuilder, with getTotalUsers(document.data['groupId']), and
getTotalUsers(groupId) async{
DocumentReference reference =
Firestore.instance.collection('groups').document(groupId.toString());
DocumentSnapshot snapshot;
snapshot = await reference.get();
return snapshot['totalUsers'];
}
...but since it is async, it just gives me 'Instance of a Future'. Now what's the easiest way to include totalUsers and 'await' it from the widget tree? I think I cannot load it in initstate, since I first have to hand over the specific userIds from the groups 'aroundme'.

How to monitor entire subcollection using transaction?

Follow this answer I am try to implement using transaction to monitor entire Firestore subcollection for new document added. Basically I only want write new document to subcollection if there is only one document. I need use transaction to avoid race condition resulting in >2 document in subcollection. Max should be 2.
How to use transaction to monitor document added to subcollection?
I am try long time to do but cannot solve.
I am experiment use iterate through subcollection for document but not know how to do this through transaction.
My code so far (maybe wrong method):
Firestore.instance.runTransaction((transaction) async {
final CollectionReference collectionRef = ref
.document(‘document’).collection(‘subCollection’);
List<DocumentSnapshot> subCollectionDocsSnapshot = [];
await collectionRef.getDocuments().then((querySnapshot) =>
querySnapshot.documents.forEach((document) {
subCollectionDocsSnapshot.add(document);
}
));
final DocumentReference newDocRef = collectionRef.document(docName);
await transaction.set(
newDocRef,
{‘docName’: docName,
}
);
});
How to solve?
Thanks!
UPDATE:
I have try add also transaction.get() to iterate through subcollection docs but it have no effect on race condition:
subCollectionDocsSnapshot.forEach((document) {
transaction.get(document.reference);
});
This isn't supported by Firestore transactions. Within a transaction, you can only find a document by its ID. You can't transact on the entire contents of a collection, and have that transaction retry if an new document is added while in the middle of the transaction. You also can't transact on the results of a query.
Instead, consider having a different document in another collection that counts the number of documents in a collection, and use that in your transaction. Or, at the very least, a document that records a boolean indicating whether or not the collection has >2 documents.

Resources