Can I have duplicate document IDs? - firebase

My application uses dates as document Id.
Each day stores the items the user bought and their total price.
The user might buy items from his local store twice or more in a day.
How can I allow that in my flutter code?
All the questions I found, ask about how not to duplicate document ID. I guess I'm the only one who want to do this.
The code just in case:
/// Save to collection [user], document [day], the list of items in [cartMap], and the total amount [total]
static Future<void> saveToFirestore(String day, Map<GroceryItem, int> cartMap, double total) async {
bool paidStatus = false;
List<String> x = day.split('-');
assert(matches(x[2], '[0-9]{2}')); // make sure the day is two digits
assert(matches(x[1], '\\b\\w{1,9}\\b')); // make sure the month is a word between 1 and 9 length
assert(matches(x[0], '[0-9]{4}')); // make sure it's a number year of four digits
final groceries = Groceries.cartMap(cartMap).groceries;
final user = await CurrentUser.getCurrentUser();
CollectionReference ref = Firestore.instance.collection(user.email);
DocumentReference doc = ref.document(day);
final dateSnapshot = await doc.get();
Firestore.instance.runTransaction((transaction) async {
updatePaidUnpaidTotal(day, user.email, total);
if(dateSnapshot.exists) {
print('Updating old document');
List<dynamic> existingItems = dateSnapshot.data['items'];
var totalItems = existingItems + groceries;
return await transaction.update(doc, {'total': FieldValue.increment(total), 'items': totalItems, 'paidStatus': false},);
}
else {
print('New document');
return await transaction.set(doc, {'total': total, 'items': groceries, 'paidStatus': paidStatus});
}
});
}

I strongly recommend not using dates (or any actual data at all) in a document ID, for the reason that you're running into.
There is rarely a requirement in an app to use a specific format for a document ID. It might be convenient in some cases, but ultimately, it constrains the future expansion of your collection.
One flexible way of adding any document at all, is to simply use add() to accept a random ID for the new document. Then you can put the date in a field in the document, and use that in your queries as a filter on that field. This is going to give you much more freedom to change things up later if you want, so you are not bound the dates for IDs. There are no real performance penalties for this.
The only reason to use a non-random string as a document ID is if you need to absolutely enforce uniqueness on that value. Since your application no longer wants to enforce this, it's better to simply remove that constraint and use random IDs.

It is not possible to have several Firestore documents in the same (sub)collection with the same document ID.
A classical approach in your case is to create one document per day and create a subcollection which contains a document for each item the user bought during this day.
Something like:
orders (collection)
- 20200725 (doc)
- orderItems (subcollection)
- KJY651KNB9 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- 20200726 (doc)
- orderItems (subcollection)
- AZEY543367 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- AZEY5JKKLJ (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }
- AZEY598T36 (doc with auto generate id): {'items': totalItems, 'paidStatus': false ... }

Related

I want to CRUD the value of the FireStore in the method

・ What I want to do.
I have stored the document ID of the currently accessed room in user collection>document>field>documentID.
I want to retrieve it and rewrite the document in the room collection.
But I can't rewrite it.
I want to know how to get the data and rewrite it.
[The document ID in the user collection is the user ID.]
I want to retrieve the field in the following method
void _onConferenceTerminated(message) async {
//[Image 1] I get the documentID being accessed from the documentID in the field of the user collection
final user = FirebaseFirestore.instance.collection('user').doc(uid()).get();
final getDocId = user.data['documentID']
//[*image2]Use it to access the room document and reduce the roomCount
final setRoomCount = await FirebaseFirestore.instance.
.collection('room')
.doc(getDocId)
.set({'roomCount': roomCount - 1});
}
//Get the user ID
String uid() {
final User user = FirebaseAuth.instance.currentUser!
final String uid = user.uid.toString();
return uid
[image1]
[image2]
You could use .update() instead of .set() if you want to change only one field. I also recommend you to use FieldValue.increment(value) which is built-in function in firestore.
Value could be both int and double.
FieldValue.increment(1)
FieldValue.increment(2.0)
Also, you can use negative numbers to decrease the value. In your case, you can try the below code.
await FirebaseFirestore.instance
.collection("room")
.doc(getDocId)
.update({"roomCount": FieldValue.increment(-1)}
Documentation: https://firebase.google.com/docs/firestore/manage-data/add-data#increment_a_numeric_value

Flutter Firebase global Auto Incremental value and retrieving in document field dart

I'm quite new to Firebase Flutter. I'm developing a mobile application to share books among others.
In firebase firestore,
I have 'users' collections which contain all the user data with unique id
I have 'books' collection which contain all the book data with unique id created automatically
Also I have 'global' collection with single document with one integer field called 'bookcount'.
Users can can have many books.
Now I want to create a another unique id field for book. idea is to have simple integer id.
One way of doing this is get list of books and find the length (count) and add 1 when creating a new record. I have ruled out this method as if many users using simultaneously, I think this can lead to duplicate ids.
So I have created a another collection global with single document and field name bookcount. Which hold number of books (rough count) on books collection. So idea is each time when adding a book to a collection increase bookcount and use this value as simple unique id for a book. This bookcount may not represent actual books as user can discard the book entry before saving it, which is okay as I only need a simple unique id.
class DatabaseService {
...
...
//final CollectionReference bookCollection = Firestore.instance.collection('users');
//final CollectionReference bookCollection = Firestore.instance.collection('books');
final CollectionReference globalData = Firestore.instance.collection('global');
...
...
Future<String> bookId() async
{
String uniquebookid = await globalData.document('SomeHardcodedID').updateData(
{
'bookcount': FieldValue.increment(1)
}).then((voidvalue) async
{
String cid = await globalData.getDocuments().then((bookvalue) => bookvalue.documents.single.data['bookcount'].toString());
return cid;
});
return uniquebookid;
}//future bookId
...
...
}//class
Now this works. well somewhat, Can we do this better? In here there are two parts, first increment the value bookcount, and then retrieve it.
Can we do this in one go?
If I try to call this method consecutively really fast when returning a value it might skip few numbers. I have call this from a button and try to press as fast I could. I think counter increase but it return
same number few times. and then skip some when press again. for example 1,2,3,4,8,8,8,8,9,10,... So at counter 4 I try to press the button multiple times. I wonder how this will behave when multiple users adding multiple books at the same time.
How Can I fix this?
Please Help, Thanks.
I think the problem was since await globalData.document('SomeHardcodedID').updateData is not producing a return value (void), as soon as this fired next call also execute which is okay, which okay for most scenarios.
However if bookId called few times within very short period (milliseconds) this produce number before FieldValue.increment(1) process.

How to filter a stream of snapshots based on multiple values in an array?

I am very new in this firestorm and database management.
In my code, there are plenty of group documents in a groups collection
Now what I want to do is, show a person from users collection the specific groups he is connected to.
here is how the groups uids look:
here is how the document of user looks:
The code I use to create the group document is this
after creating the document I add the uid of the new document to the user's array called groups.
Future createNewGroupData(String groupName) async {
String _userID = await getUID();
return await groupCollection.add({
'creatorUID': _userID,
'groupName': groupName,
'teachers': [_userID],
}).then((ref) => {
userCollection.document(_userID).updateData({'groups': FieldValue.arrayUnion([ref.documentID])})
});
this is the structure of how I create the document for each user,
Future updateUserData(int avatarID, String firstName, String lastName, String status, List groups, String school) async {
return await userCollection.document(uid).setData({
'avatarID': avatarID.round(),
'firstName': firstName,
'lastName': lastName,
'status': status,
'groups': groups,
'school': school,
});
}
How I implemented that was when the user was added to a group. The uid of that group was appended in an array under that user's documents.
Now what I want to do is get a stream of snapshots of the groups only uids are present in that user's array. I can not really find any way to implement that.
If I understand correctly you want to load the group documents whose document ID matches what you find in the groups field of a user. If that is the case, you are looking for a combination of FieldPath.documentId and an in query.
Something like:
Firestore.instance.collection('groups').where(FieldPath.documentId, whereIn: listOfUids)
As shown in the Firestore documentation on query limitations this will only work for up to 10 document IDs:
Cloud Firestore provides limited support for logical OR queries. The in and array-contains-any operators support a logical OR of up to 10 equality (==) or array-contains conditions on a single field. For other cases, create a separate query for each OR condition and merge the query results in your app.
If you have more document IDs, you will need to fire a separate query for each (up to) 10.
Also see:
Flutter Firebase get documents by array of IDs

How add data to a map in Firestore document Flutter

I am trying to add key value pairs to a map of maps in a firestore document. You can see the document set up below. The "Bills" map consists of key value pairs representing each bill. Each bill is represented by a map. For example: "Bill 1" consists of key value pairs which represent the name of the item and the cost of the item.
I want to add key value pairs to the bill. For example, I want to add an item to "Bill 1", so that it consists of both "Bacon: 14" and the other items I add.
Here is the code I have right now. However, this code simply replaces Bacon with the new item I add. It doesn't add the new value to the existing map, simply replaces the entire map (the contents of Bill 1) with the new item and cost.
Here is the current code:
static Future addItemToBill(GroceryItem groceryItem, double cost) async {
try{
final FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
int currentBillNumber = await getBillCount(currentUser.uid);
if(currentBillNumber == 0){
currentBillNumber = 1;
}
final DocumentReference documentReference = usersCollection.document(currentUser.uid);
await documentReference.updateData({
"Bills.Bill $currentBillNumber": {
"${groceryItem.itemName}" : cost
}
});
}catch(error){
}
}
Current Code
Thank you for the help!
When you call updateData, Firestore will always replace everything behind each key you provide. In your case, the key is "Bills/Bill $currentBillNumber", and it will contain only what you provide, replacing what's already there at the given key.
If you want to add something new, your will will have to be specific enough that it does not replace other values.
await documentReference.updateData({
"Bills.Bill $currentBillNumber.${groceryItem.itemName}": cost
});
I guess could be better you create a collection of bills inside of each user, then you will have a collection of documents (bills), inside of each document (bill) you create another collection called items and then ou will have a list of documents (items), will be easy to query later i think.
You should have like this:
-collection(users)
--document(user)
---collection(bills)
----document(bill)
-----collection(items)
------document(item)
Got it?
What about having all bills as a new, highest level, collection and then use the userUID as a key in each bill document?
Then, if you want all the bills for a single user:
db.collection('allBills').
.whereEqualTo("userUID", currentUser.uid)
.get()
Less nesting of collections and documents.

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.

Resources