Reading data from Firestore in Swift - ios

I am currently developing an IOS app, and I need a database! I've chosen the google firestore! I need to read some fields I create that have subfields!
Something like this:
db.collection("usersorders").document(uid).collection("order").addDocument(data: ["items":0, "order":["Book1":0,"Book2":0,"Book3":0]]){ (error) in
if error != nil {
// Show error message
print("Error saving user data")
}
}
Where I need to read the "Book1" value for example! I've looked in a lot of places, but I can't seem to find what I am looking for. Read subfields, from a field of a document!
#IBAction func AddtoCart(_ sender: Any) {
let uid = user!.uid
let docRef = db.collection("usersorders").document(user!.uid).collection("order").document()
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let Book1 = document.get("Book1")
let Items = document.get("items")
let Book1now = Book1 as! Int + 1
let Itemsnow = Items as! Int + 1
}
}}
This is what I have been doing but it doesn't work! After writing the code to update the database with the Items/Book1 now values it just doesn't update! Please Help me

Given that your document data looks like this:
["items":0, "order":["Book1":0,"Book2":0,"Book3":0]]
You'll first need to access the order field in your document, before you can then find an item in that field
let order = document.get("order")
As far as I can see, this makes order a dictionary, so you can get the specific value from it with:
let book1 = order["Book1"] as Int

Related

How to prevent new addition data replacing the previous data in Firestore?

EDIT:
I’m trying to add additional data into Cloud Firestore under the same UID. However, the new data deletes the old data and rewrites on it. Because as for the result later, I am desired to show every data of the same UID on my UI.
I have also checked on this example but it was in javascript and I am not sure on how to apply it on my code. Thank you.
this is my program code.
#IBAction func newBookTapped(_ sender: Any) {
guard let uid = Auth.auth().currentUser?.uid,
let data = bookData() else {
return
}
db.collection("bookData").document(uid).setData(data)
}
func bookData() -> [String: Any]? {
guard let title = bookTitleTextField.text,
let author = bookAuthorTextField.text,
let summary = bookSummaryTextField.text else {
return nil
}
let data: [String: Any] = [
"bookTitle": title,
"bookAuthor": author,
"bookSummary": summary
]
self.transitionToMenu()
return data
}

How to get progress of asynchronous Firebase data read?

I have some code that reads data from Firebase on a custom loading screen that I only want to segue once all of the data in the collection has been read (I know beforehand that there won't be more than 10 or 15 data entries to read, and I'm checking to make sure the user has an internet connection). I have a loading animation I'd like to implement that is started by calling activityIndicatorView.startAnimating() and stopped by calling activityIndicatorView.stopAnimating(). I'm not sure where to place these or the perform segue function in relation to the data retrieval function. Any help is appreciated!
let db = Firestore.firestore()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else{
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
}
You don't need to know the progress of the read as such, just when it starts and when it is complete, so that you can start and stop your activity view.
The read starts when you call getDocuments.
The read is complete after the for loop in the getDocuments completion closure.
So:
let db = Firestore.firestore()
activityIndicatorView.startAnimating()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else {
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
DispatchQueue.main.async {
activityIndicatorView.stopAnimating()
}
}
As a matter of style, having multiple arrays with associate data is a bit of a code smell. Rather you should create a struct with the relevant properties and create a single array of instances of this struct.
You should also avoid force unwrapping.
struct PackageInfo {
let id: String
let name: String
let imageId: String
let radius: String
}
...
var packages:[PackageInfo] = []
...
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else if let documents = snapshot?.documents {
self.packages = documents.compactMap { doc in
if let title = doc.get("title") as? String,
let imageId = doc.get("imgID") as? String,
let radius = doc.get("radius") as? String {
return PackageInfo(id: doc.documentID, name: title, imageId: imageId, radius: radius)
} else {
return nil
}
}
}
There is no progress reporting within a single read operation, either it's pending or it's completed.
If you want more granular reporting, you can implement pagination yourself so that you know how many items you've already read. If you want to show progress against the total, this means you will also need to track the total count yourself though.

Firestore search for String in Array in Document

I want to search for an specific string value in an document which is in an array.
Here is my database:
This is my code so far: But it returns 0 documents:
func changePhotoUrlInPosts(url: String) {
let db = Firestore.firestore()
let user = UserService.currentUserProfile!
db.collection("posts")
.whereField("username", isEqualTo: user.username)
.getDocuments { (snapshot, error) in
if let indeedError = error {
print(indeedError.localizedDescription)
return
}
guard let indeedSnapshot = snapshot else {
print("snapshot is empty")
return
}
for document in indeedSnapshot.documents {
document.setValue(url, forKey: "photoUrl")
}
}
}
How can I go into my array in this document?
Thanks
Your screenshot is showing data in Realtime Database, but your code is querying Firestore. They are completely different databases with different APIs. You can't use the Firestore SDK to query Realtime Database. If you want to work with Realtime Database, use the documentation here.
There is author between posts and username field in your data structure.
Your code means that right under some specific post there is username field.
So such code will work because date right undes post:
db.collection("posts").whereField("date", isEqualTo: "some-bla-bla-date")
In your case you have two options as I see:
duplicate username and place this field on the same level as
date and guests.
re-write you code to check username inside author document.
Hope it will help you in your investigation.
So I changed my code to:
func loadData(url: URL){
let ref = Database.database().reference().child("posts")
let user = UserService.currentUserProfile!
ref.queryOrdered(byChild: "author/username").queryEqual(toValue: user.username).observe(.value, with: { snapshot in
if var post = snapshot.value as? [String : Any] {
print("updated all Posts")
post.updateValue(url.absoluteString, forKey: "photoUrl")
print(post.values)
}else{
print("fail")
}
})
}
It went through and I get the print statement of my values but the data didn't changed in the realtime database

Swift - Firebase - order collection

I am trying to retrieve some documents but I need them to be ordered by some data ("ListIDX") inside my "Wishlists" - collection.
I tried this but that's not allowed:
db.collection("users").document(userID).collection("wishlists").order(by: "ListIDX").document(list.name).collection("wünsche").getDocuments()
This is my function:
func getWishes (){
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
var counter = 0
for list in self.dataSourceArray {
print(list.name) // -> right order
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments() { ( querySnapshot, error) in
print(list.name) // wrong order
if let error = error {
print(error.localizedDescription)
}else{
// DMAG - create a new Wish array
var wList: [Wish] = [Wish]()
for document in querySnapshot!.documents {
let documentData = document.data()
let wishName = documentData["name"]
wList.append(Wish(withWishName: wishName as! String, checked: false))
}
// DMAG - set the array of wishes to the userWishListData
self.dataSourceArray[counter].wishData = wList
counter += 1
}
}
}
}
This is what I actually would like to achieve in the end:
self.dataSourceArray[ListIDX].wishData = wList
Update
I also have a function that retrieves my wishlists in the right order. Maybe I can add getWishesin there so it is in the right order as well.
func retrieveUserDataFromDB() -> Void {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
db.collection("users").document(userID).collection("wishlists").order(by: "listIDX").getDocuments() { ( querySnapshot, error) in
if let error = error {
print(error.localizedDescription)
}else {
// get all documents from "wishlists"-collection and save attributes
for document in querySnapshot!.documents {
let documentData = document.data()
let listName = documentData["name"]
let listImageIDX = documentData["imageIDX"]
// if-case for Main Wishlist
if listImageIDX as? Int == nil {
self.dataSourceArray.append(Wishlist(name: listName as! String, image: UIImage(named: "iconRoundedImage")!, wishData: [Wish]()))
// set the drop down menu's options
self.dropDownButton.dropView.dropDownOptions.append(listName as! String)
self.dropDownButton.dropView.dropDownListImages.append(UIImage(named: "iconRoundedImage")!)
}else {
self.dataSourceArray.append(Wishlist(name: listName as! String, image: self.images[listImageIDX as! Int], wishData: [Wish]()))
self.dropDownButton.dropView.dropDownOptions.append(listName as! String)
self.dropDownButton.dropView.dropDownListImages.append(self.images[listImageIDX as! Int])
}
// // create an empty wishlist
// wList = [Wish]()
// self.userWishListData.append(wList)
// reload collectionView and tableView
self.theCollectionView.reloadData()
self.dropDownButton.dropView.tableView.reloadData()
}
}
self.getWishes()
}
For a better understanding:
git repo
As #algrid says, there is no sense to order the collection using order() if you are going to get an specific element using list.name at the end, not the first or the last. I would suggest to change your code to:
db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments()
I am trying to retrieve some documents but I need them to be ordered by some data ("ListIDX")
The following line of code will definitely help you achieve that:
db.collection("users").document(userID).collection("wishlists").order(by: "ListIDX").getDocuments() {/* ... */}
Adding another .document(list.name) call after .order(by: "ListIDX") is not allowed because this function returns a Firestore Query object and there is no way you can chain such a function since it does not exist in that class.
Furthermore, Firestore queries are shallow, meaning that they only get items from the collection that the query is run against. There is no way to get documents from a top-level collection and a sub-collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use the properties of documents in a single collection. So the most simple solution I can think of would be to use two different queries and merge the results client-side. The first one would be the above query which returns a list of "wishlists" and the second one would be a query that can help you get all wishes that exist within each wishlist object in wünsche subcollection.
I solved the problem. I added another attribute when saving a wish that tracks the index of the list it is being added to. Maybe not the smoothest way but it works. Thanks for all the help :)

How can I convert the downloaded data from Firestore to an array and assign it to my table view?

I'm capturing Firestore data as Firebase shows us, but I don't know how to save the query I make.
In conclusion, what I want to do is bring all the documents that have the same value in your pid field, and then show in a table the product fields and start date, each document in a different cell.
collection food
document: 1
pid:john1
product:Ice
startDate:01/01/2010
document: 2
pid:john1
product:Rice
startDate:01/02/2010
I need to show in the table:
Ice was bought on 01/01/2010
Rice was bought on 01/02/2010
I have this code:
func loadFood(){
pid = "john1"
db = Firestore.firestore()
db.collection("food").whereField("pid", isEqualTo: pid)
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("\n--------------------------------------")
print("Error document: \(error!)")
print("--------------------------------------\n")
return
}
let startDate = documents.map { $0["startDate"]! }
let product = documents.map {$0["product"]!}
let message = ("\(product) was bought \(startDate)")
self.dataRecord.insert(message, at: 0)
DispatchQueue.main.async {
self.tvRecord.reloadData()
}
}
}
I'm showing in the table:
[Ice, Rice] was bought on [01/01/2010, 01/02/2010]
You make a couple of mistakes. First, you loop over the documents multiple times, unnecessarily; that's not very efficient. In your case, you should loop over them once and do all of your data prep in each loop iteration. Second, Firestore has a method specifically for extracting data from document fields called get() which is very easy to read and efficient.
func loadFood(){
pid = "john1"
Firestore.firestore().collection("food").whereField("pid", isEqualTo: pid).addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("\n--------------------------------------")
print("Error document: \(error!)")
print("--------------------------------------\n")
return
}
for doc in documents {
guard let startDate = doc.get("startDate") as? String,
let product = doc.get("product") as? String else {
continue // continue this loop, not "return" which will return control out of the calling function
}
let message = "\(product) was bought \(startDate)"
dataRecord.append(message)
}
DispatchQueue.main.async {
self.tvRecord.reloadData()
}
}
}
As you deal with the 2 arrays as if they are 1 string instead you need
struct Item {
let message,startDate:String
}
Then
var dataRecord = [Item]()
and finally
self.dataRecord = documents.map { Item(message:$0["product"]!,startDate:$0["startDate"]!)}

Resources