Error adding Firestore documents to Pickerview - ios

I've recently changed a lot on my iOS application and now I got stuck.
I'm trying to insert data from Firestore which looks like this:
So, as you can see I've 6 different names in here.
And here is the code to insert into pickerView.
func getPerson()
{
let authentication = Auth.auth().currentUser?.uid
db.collection("users").document(authentication!).collection("person").getDocuments { (QuerySnapshot, err) in
//If error is not equal to nil
if err != nil
{
print("Error getting documents: \(String(describing: err))");
}
//Succeded
else
{
//For-loop
for _ in QuerySnapshot!.documents
{
//Cleaning the array for the new values
self.personArray.removeAll()
let document = QuerySnapshot!.documents
let data = document.data() //HERE IS THE ERROR
data.forEach { (item) in
if let person1Data = data["name"] as? String
{
self.personArray.append(person1Data)
print(self.personArray)
}
}
}
self.pickerView.reloadAllComponents()
}
}
}
I'm getting the error:
Value of type '[QueryDocumentSnapshot]' has no member 'data'
It used to have QuerySnapshot!.documents.first
but it does not work anymore when I've changed the Firestore data.
Edit:
So. the output is now:
["Joche"] ["Joche", "Joche"] ["Putte"] ["Putte", "Putte"] ["Rebecca"]
["Rebecca", "Rebecca"] ["Fredrik"] ["Fredrik", "Fredrik"] ["Anna"]
["Anna", "Anna"] ["Vickan"] ["Vickan", "Vickan"]
which means it adds everything but x3. How to solve this problem?

data is an instance method of a single QueryDocumentSnapshot not an array , You need
self.personArray.removeAll()
for elem in querySnapshot!.documents {
let data = elem.document.data()
data.forEach {
if let person1Data = $0["name"] as? String {
self.personArray.append(person1Data)
print(self.personArray)
}
}
}

Related

Listen for documents just when actually added to collection - swift

I have a collection on Firestore and I listen for changes like this:
func createMatchesListener(){
let db = Firestore.firestore()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
// do things
}
})
})
}
I only want to listen for documents that are actually added to that collection.
In fact, the problem is that whenever I invoke this function I receive all the documents of the collection as added documents and then I also receive documents added later.
How can I listen just for actually added later documents, ignoring the ones already present in the collection? Searching online I didn't find any solution to this issue.
EDIT:
This is the way I tried to solve the problem:
func createMatchesListener(){
guard let currentUid = Auth.auth().currentUser?.uid else { return }
getUidsAlreadyMade { uidsAlreadyMade in
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
let data = change.document.data()
let userId = data["uid"] as? String ?? ""
if uidsAlreadyMade.contains(userId) == false{
//means the uid is newly created in the collection, do stuff accordingly
arrayOfUidsAlreadyMade.append(currentUid)
}
}
if change.type == .removed{
// if the document has been removed, remove also the id from the array of uids
let data = change.document.data()
let currentUid = data["uid"] as? String ?? ""
arrayOfUidsAlreadyMade.removeAll { $0 == currentUid }
}
})
})
}
}
func getUidsAlreadyMade(completion: #escaping ([String]) -> Void){
guard let currentUid = Auth.auth().currentUser?.uid else { return }
db.collection("Matches").document(currentUid).collection("Matches").getDocuments { snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
arrayOfUidsAlreadyMade.removeAll()
snapshot?.documents.forEach({ doc in
let dict = doc.data()
let userId = dict["uid"] as? String ?? ""
arrayOfUidsAlreadyMade.append(userId)
})
completion(arrayOfUidsAlreadyMade)
}
}
A simple solution is to include a timestamp in your Firestore documents.
Suppose your documents store Tasks, for example
documentId
task: "get dinner"
timestamp: 20211123
and suppose your app doesn't care about past tasks, only new ones.
When the tasks are read, update the timestamp as to when that occurred.
Then each time after that you want to read only 'new data' specify that in your listener, keeping track of when the last read timestamp was:
db.collection("task").whereField("timestamp", isGreaterThan: lastReadTimeStamp).addSnapshotListener...
The above will only read in tasks that occured after the prior timestamp and add a Listener (reading in all of the new tasks so you can populate the UI).
You can store an array with the ID of the documents that you already have stored in the device. That way, all that you need to do before doing things is checking that document's id is not in your array
There's no way of preventing Firestore from returning the initial snapshot of documents when a document listener is added, so just use a boolean to keep track of the initial snapshot and ignore it.
var listenerDidInit = false
func createMatchesListener(){
let db = Firestore.firestore()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
if listenerDidInit {
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
// do things
}
})
} else {
listenerDidInit = true
}
})
}
private var listener: ListenerRegistration?
self.listener = db.collection("Matches") // matchesListener
listener!.remove()

Can't pass variable value from firebase firestore to another class SWIFT

So I have this function in class Functions :
struct Prices {
var standardPrice: Int!
}
// FUNC PRICING
class Functions {
private var PricingRef: CollectionReference!
var price = Prices()
func getPrice() -> Prices {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
}
}
}
return price
}
}
Then I want to pass the standardPrice value to this class, called PriceList :
class PriceList: UITableViewController {
var price = Prices()
var newStandardPrice = 0
func Price() {
price = Functions().getPrice()
newStandardPrice = price.standardPrice // always error with value nil
}
I always have that error where newStandardPrice is nil.
but the print(self.price.standardPrice!) shows number of result I want.
So as far as I know, the problem here is because it takes time for the firebase firestore to get the data from database.
How do I get the value of standardPrice after its assigned with the new price from firebase database?
Any help will be appreciated
Thankyou
you need to use completion handler because its async function
func getPrice(completion:#escaping (Prices?,Error?)-> Void) {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
completion(nil,err)
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
completion(self.price.standardPrice,nil)
}
}
}
}
How to use
Functions().getPrice { (price, error) in
if let err = error {
// do something if you get error
} else if let getPrice = price {
// use price
self.price = getPriice
}

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"]!)}

altering a variable outside a closure

I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)

Getting Four Random, but Unique, Documents from a Cloud Firestore - Swift

Trying to use Dan McGrath's suggested Document Agnostic solution to querying Firestore for random documents, along with the Rinse in Repeat suggestion for pulling multiple random documents.
This code occasionally comes up with nil documents (doesn't always return a document). I think my query is off and am looking for guidance/ideas on how to correct he problem - Thanks
func getRandomPlateOne() {
let plateOneRef = db.collection("plates")
plateOneRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
plateOneRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
EDIT -Though I had this figured out, that passing the random number into a variable, and then using that variable in my query would make certain that the same random number was being used whether the query was going greaterThan or lessThanAndEqualTo. Still getting an occasional nil back from Firestore. My query must still be off.
New code:
func getRandomPlateOne() {
let collectionRef = db.collection("plates")
collectionRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
collectionRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}
var randomNumberOne: UInt64 = 0
EDIT - Function has evolved. I am still unable to get the step between checking if first condition returned a document or not, and moving to a sometimes necessary second query. This works, but I am using a fixed UInt64.
var randomNumberOne: UInt64 = 8190941879098207969 (higher than any other in my collection)
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
print("This is the snapshot from the second query. \(snap!) ")
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
As I said in my above comment, I was using two different random numbers for working up the range of documents, or down the range of documents when necessary.
I created a generateARandomNumber function, that is called in my viewDidLoad function.
func generateARandomNumber() {
randomNumber = UInt64.random(in: 0 ... 9223372036854775807)
}
That number is then passed into a variable, that is used within my getARandomPlate(a Firestore document).
I am now using the same random number, whether searching for a document whose random number isGreaterThan the viewDidLoad generated random number or if I end up querying for a isLessThanOrEqualTo document.
EDIT -
Working code:
let db = Firestore.firestore()
var randomNumberOne: UInt64 = 0
var plates = [Plate]()
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}

Resources