Swift: How do I repeat a getDoc request to Firestore? - swift

I can already check if a doc exists in a collection. However I am unable to repeatedly check the same collection while trying different path names.
For example, my collection name is the UID of the user. There can be an unlimited amount of docs in this collection. The docs are titled "UID-0", "UID-1", "UID-2" and so on as the user adds items.
Every time it finds a doc that already exists such as "UID-0" it will change the path request to "UID-+=1" until the number exceeds the docs and it is able to create and use that path name.
Each doc contains about a dozen fields of the same data model but of course different data.
var docAlreadyExists: Bool = true
var multipleUserFencesIdCount: Int = 0
var newID: String = ""
let id = self.auth.currentUser?.uid ?? ""
repeat {
print("1")
self.fencesInfoCollection.document("Live").collection(id).document(newID).getDocument(completion: { document, error in
print("2")
if let document = document, document.exists {
print("EXISTS")
multipleUserFencesIdCount += 1
newID = newID.dropLast() + "\(multipleUserFencesIdCount)"
} else {
print("DOES NOT EXIST")
docAlreadyExists = false
}
})
} while docAlreadyExists
With that said, how can I repeatedly check if a document exists until the path name exceeds the rest and is able to create a new doc with the new data.
Edit:
The 1 gets repeatedly called correctly but the .getDoc never calls since 2 is never printed.

I figured out a better solution to my goal, instead of trying to repeat a call with different IDs I am now getting all documents and counting how many are in the collection.
self.fencesInfoCollection.document(id).collection("Live").getDocuments(completion: { document, error in
if let document = document, document.isEmpty {
print("EMPTY")
} else {
print("DOC1: \(String(describing: document?.count))")
}
})

Related

How to pull all product data from index in Algolia?

After reading the docs on how to search and browse an index with Algolia's Swift Client, it's not clear how I need to pull all product data from an index. In the documentation, it is stated that:
The search query only allows for the retrieval of up to 1000 hits. If
you need to retrieve more than 1000 hits (e.g. for SEO), you can
either leverage the Browse index method or increase the
paginationLimitedTo parameter
So I wrote the following:
let client = SearchClient(appID: "...", apiKey: "...")
var index: Index
index = client.index(withName: "products")
var productFeed:[Product] = []
let settings = Settings()
.set(\.paginationLimitedTo, to: 4500)
index.setSettings(settings) { result in
if case .success(let response) = result {
.....
}
}
Then to Browse:
index.browse(query: Query("")) { result in
if case .success(let response) = result {
do {
let products:[Product] = try response.extractHits()
DispatchQueue.main.async {
self.productFeed = products
}
}catch let error{
print("Hits decoding error :\(error)")
}
}
}
It would seem as though the two blocks of code would work together, but my productFeed array just returns 1000 records. Can someone explain what I am doing wrong here?
To retrieve all records from your index use the browseObjects method.
This method performs multiple consecutive browse method calls extracting all records from an index page by page.

How can I return a count of second level collection documents in Firebase?

I am trying to provide a count of the number of items in the ‘Products’ collection for each user, and display this in a label on the UI of my app. This is how my Firebase database is structured:
Users
user 1
products
product 1
product 2
user 2
products
product 1
So for example, when user 1 logs in, it will display that they have 2 products, whereas when user 2 logs in, it will display that they have 1 product.
Below is my code which is used to return this data for each user.
document.reference.collection("products").get().then(function(querySnapshot) {
However, I am getting the below errors for this line of code.
Error 1 - “Cannot find 'querySnapshot' in scope”
Error 2 - “Cannot find 'function' in scope”
Here is my full method I am using to retrieve and display a count of items for the currently signed in user.
func databaseCount() {
let db = Firestore.firestore()
let user = Auth.auth().currentUser
db.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let userId = data["uid"] as! String
if userId == user?.uid {
document.reference.collection("products").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
let itemCount = data(querySnapshot.size) as! String
self.welcomeLabel.text = "Your item count is \(itemCount)!"
});
}
}
}
}
}
How would I go about updating my code to achieve my intended goal?
What i usually do in such scenarios is, When you create document in Users collection create an extra field of name cartItemCount with value 0 by default. In your case productsCount.
Users
---------user1
------------------name
------------------email
------------------productCount
------------------Products
--------------------------------product1
So when ever a document is added to Product collection of a User. Increment the value of productsCount by 1 and decrement by 1 if document is removed using the reference to that user document.
FieldValue.increment(Int64(1))
FieldValue.increment(Int64(-1))

Delete a specific value in an array from Firestore Swift

I have this code here and I want to delete certain value inside the array "Answered". Is there a simple way to access the first value in the array? This is right but shows what I want to happen "Answered[0]" <- I want to get the first value in that array and delete it. Thank you in Advance
let uid = Auth.auth().currentUser?.uid
print(self.randomArray)
let wash = db.collection("users").document(uid!)
wash.updateData([
"Answered": FieldValue.arrayUnion([self.randomArray])
])
}
if(self.check.isEmpty != true){
self.whichQuestion = self.check[0]
self.whichQuestionString = String(self.whichQuestion)
db.collection("users").document(uid!).updateData([
"Answered": FieldValue.delete(),
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
If your array contains unique values however, you can remove the item with:
self.whichQuestionString = String(self.whichQuestion)
db.collection("users").document(uid!).updateData([
"regions": FieldValue.arrayRemove([whichQuestionString])
])
If you only know the index of the item, there is no way to remove it without knowing the entire array.
The recipe for this is:
Read the document from Firestore
Modify the array in your application code
Write the entire modified array back to Firestore
Also see (none of which unfortunately are for Swift):
Is there any way to update a specific index from the array in Firestore
Delete data from Firestore dynamically

How can I create a snapshot to listen to users being ADDED to my Firestore database?

I am using Firestore. I am trying to listen for when a new user is added. The problem is, each user also has a friends dictionary. So when I use a snapshot, my code is detecting both events of (1) A new user being added and (2) a new friend being added.
I have tries iterating over the document changes data and restricting doc.document.data()["friends"] == nil. Why isn't this working/how can I properly add a restriction to only include when a new user is added?
func observeUsers(onSuccess: #escaping(UserCompletion)) {
Ref().firestoreUserRef.collection("users").addSnapshotListener { (querySnapshot, error) in
if error != nil {
print("error with observeUser snapshot")
return
}
querySnapshot?.documentChanges.forEach { doc in
//I want to detect that a new user was added, I do not want to detect if a friend was added
if (doc.type == .added) && doc.document.data()["friends"] == nil {
guard let dict = querySnapshot else { return }
for document in dict.documents {
var dictionary = [String : Any]()
dictionary = document.data()
if let user = User.transformUser(dict: dictionary) {
onSuccess(user)
}
}
}
}
}
}
The easiest way is starting from the data model to support the types of queries you want. In this case, when you create a new user (which I assume always has no friends), set the friends field explicitly to null. This will let you query for all new users:
Ref().firestoreUserRef.collection("users").whereField("friends", isEqualTo: NSNull()).addSnapshotListener ...
An assumption here is your transformUser process will update the document and replace null with either a list of friends of an empty array so that it no longer matches the query.

Swift: Wait for firestore load before next load

I have a view controller that lists data from a firestore database. Inside a firestore collection, I have a bunch of documents with the information shown in the list, and one document called order which contains one field which is an array of strings in the order I want them displayed. My code grabs this:
self.db.collection("officers").document(school).collection(grade).document("order").getDocument {(document, error) in
if let document = document, document.exists {
self.officerNames = (document.data()!["order"] as! Array<String>)
and then is supposed to use the strings in the array order (officerNames) to query the documents in that same collection (all the documents have a different role so it's only getting one document in the snapshot) and display them in the same order as the one set in order (officerNames).
for item in 1...self.officerNames.count {
self.db.collection("officers").document(school).collection(grade).whereField("role", isEqualTo: self.officerNames[item-1]).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let officerMessage = document.data()["agenda"] as! String
let officerInfo = document.data()["short"] as! String
(a bunch of code here using that ^ ^ and due to the color I need item to be an integer)
}
}
}
}
I know that if I try printing item before the self.collection("officers")..... the numbers count by one but if I do that in the for document in querySnapshot..... they're all out of order meaning some documents are loaded faster than others. I have read about Async functions in Swift (although I do use those in JavaScript) but am really confused how to use them and hopefully, there is a simpler way to do this. Any way I can wait to make sure the previous document has been loaded and analyzed before iterating through the loop again?
Here's a screenshot of the database:
Honestly, you may want to examine your data structure and see if you can create one that doesn't require multiple queries like this. I can't quite tell what your data structure is, but if you update your question to include it, I can give some suggestions for how to refactor so you don't have to do 2 different get requests.
That being said, since Swift doesn't have promises like JS, it can be tough to keep data in order. For most cases, closures work well, as I wrote about in this blog. But they still won't preserve order in an array of async calls. Assuming you're using some array to store the officer's data, you can declare the size of the array up front by giving each one a default value. This would look something like this:
var officerArray = [Officer](repeating:Officer(), count: self.officerNames.count)
Of course, it'll be different depending on what kind of objects you're populating it with. I'm using some generic Officer object in this case.
Then, rather than appending the newly created Officer object (or whatever you're calling it) to the end of the array, add its value to its particular location in the array.
for item in 1...self.officerNames.count {
self.db.collection("officers").document(school).collection(grade).whereField("role", isEqualTo: self.officerNames[item-1]).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let officerMessage = document.data()["agenda"] as! String
let officerInfo = document.data()["short"] as! String
// etc
officerArray[self.officerNames.count-1] = Officer(officerMessage: officerMessage, officerInfo: officerInfo) // or however you're instantiating your objects
}
}
}
}
This preserves the order.