Multiple Firestore snapshots overwrite each other - arrays

Im checking if firestore arrays contains strings from another array (not Firestore array). Then I place the data in a dictionary
func loadData(){
for i in 0..<testTable.count {
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: testTable[i] ).getDocuments(){
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
}else if let querySnapshot = querySnapshot {
if (querySnapshot.isEmpty == false){
self.ingredientsArray = querySnapshot.documents.compactMap({Ingredients(dictionary: $0.data())})
print("\(self.ingredientsArray)")
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
The problem is that dictionary keeps the data from only the last snapshot as expected. Is there a way to keep all the data from all the snapshots?

This happens as you re-assign the array self.ingredientsArray so Replace this
self.ingredientsArray = querySnapshot.documents.compactMap({Ingredients(dictionary: $0.data())})
with
let res = querySnapshot.documents.compactMap({Ingredients(dictionary: $0.data())})
self.ingredientsArray.append(contentsOf:res)

Related

Retrieve data from 'getDocuments' query from Firestore retrieve's empty array

I am retrieving data from my Firestore database using a 'getDocuments' query, however I am retrieving an empty array. Through debugging, I have used a print(snapshot?.documents) and found an empty array returned. Can someone help as to why I am returning an empty array?
getNotifications function:
public func getNotifications(
completion: #escaping ([IGNotification]) -> Void
) {
guard let username = UserDefaults.standard.string(forKey: "username") else {
completion([])
return
}
let ref = firestoreDatabase.collection("users").document(username).collection("notifications")
ref.getDocuments { snapshot, error in
guard let notifications = snapshot?.documents.compactMap({
IGNotification(with: $0.data())
}),
error == nil else {
completion([])
return
}
completion(notifications)
}
}
Firestore data:
Screenshot of Firestore data

How to retrieve data held in an array from Firestore using Swift

this seems like it should be quite a simple thing to do, but can't seem to find a solution. I have some data in Firestore held in an array that I need to get and place into and two dimensional array inside my swift app. I'm pretty noob so apologies in advance!
This is the data I'm trying to get from Firestore
This is the code I'm using to query my documents and then loop through the result, extracting the data
fireStoreDatabase.collection("Posts").whereField("postedTo", arrayContains: userId).order(by: "date", descending: true).addSnapshotListener { (snapshot, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if snapshot?.isEmpty != true && snapshot != nil {
print("Posted data got")
//clear the arrays to stop duplicates when we do an upload and pull in the data again
self.postedShoutTextArray.removeAll(keepingCapacity: false)
self.postedByArray.removeAll(keepingCapacity: false)
self.postedDateArray.removeAll(keepingCapacity: false)
self.postedQuestionsArray.removeAll(keepingCapacity: false)
//loop through the firestore
for document in snapshot!.documents {
//add data to the arrays
if let shoutText = document.get("shoutText") as? String {
self.postedShoutTextArray.append(shoutText)
}
if let postedBy = document.get("postedBy") as? String {
self.postedByArray.append(postedBy)
}
if let date = document.get("date") as? String {
self.postedDateArray.append(date)
}
if let pollQuestions = document.get("pollQuestions") as? [String] {
self.postedQuestionsArray = pollQuestions
} else {
print("no array data")
}
self.receivedCollectionView.reloadData()
}
} else {
print("GET POSTED MESSAGES no data")
}
}
}
So I'd like the data to go into a two dimensional array (if that's possible) containing the data from the pollQuestions array for each document I loop through (does that make sense?).
Been searching all over, and seen references to map etc, but had no luck finding a solution.
Any help appreciated!
Rather than storing each property in a separate array, you may want to consider representing it with a struct. Something like:
struct Item {
var shoutText: String?
var postedBy: String?
var date: String?
var pollQuestions : [String]
}
Then, on your view controller, declare a property:
var items: [Item] = []
Then, in your snapshot listener, you can populate that array:
func getData() {
Firestore.firestore().collection("Posts").whereField("postedTo", arrayContains: userId).order(by: "date", descending: true).addSnapshotListener { (snapshot, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if let snapshot = snapshot, !snapshot.isEmpty {
print("Posted data got")
self.items = snapshot.documents.map { document in
Item(shoutText: document.get("shout") as? String,
postedBy: document.get("postedBy") as? String,
date: document.get("date") as? String,
pollQuestions: document.get("pollQuestions") as? [String] ?? [])
}
self.receivedCollectionView.reloadData()
} else {
print("GET POSTED MESSAGES no data")
}
}
}
}
Later, you can access this data:
self.items[itemIndex].pollQuestions[pollQuestionIndex]

Why does this function append the object twice to the array each time it runs?

I'm using swift's DispatchGroup() to help orchestrate a for loop that
finds a document in firesbase
converts the document to a custom object
appends the custom object to an array
With each pass, the function ends up appending each object twice to the array and I can't understand why.
Here is the function...
func getFriends() {
// Initialize the DispatchGroup
let group = DispatchGroup()
// the myFriends array contains documentIDs that I am using to fetch documents from firebase
//
for pid in myFriendObj.myFriends {
group.enter()
_ = Firestore.firestore().collection("Players")
.whereField(FieldPath.documentID(), isEqualTo: pid)
.addSnapshotListener { [self] querySnapshot, error in
if let error = error {
print("Error getting > Players: \(error.localizedDescription)")
return
}
guard let querySnapshot = querySnapshot else { return }
self.players.append(
contentsOf: querySnapshot.documents.compactMap { document in
try? document.data(as: UserProfile.self)
})
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
// I'm currently eliminating the dups via this fancy extends method.
self.players = self.players.removeDuplicates()
}
}
:: UPDATE ::
Still no luck on this - i've even removed dispatchgroup and the snapshotlistener callbacks and still this code calls get() twice when an instance of the class is instantiated. Here is the new, more simple code...
class FriendRepository: ObservableObject {
private let store = Firestore.firestore()
private let friendPath: String = "MyFriends"
#Published var friendIDs: [String] = []
var userId = ""
private let authenticationService = AuthenticationService()
private var cancellables: Set<AnyCancellable> = []
init() {
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.get()
}
.store(in: &cancellables)
}
func get( ) {
store.collection(friendPath).document(userId).getDocument {(document, error) in
let result = Result {
try document?.data(as: Friends.self)
}
switch result {
case .success(let f):
if let f = f {
print("friends:>> \(f.myFriends)")
self.friendIDs = f.myFriends
} else {
print("Document does not exist")
}
case .failure(let error):
print("Error decoding city: \(error)")
}
}
}
When a new instance run init(), I see this in the console... It prints the friends:>> statement twice
friends:>> ["PHyUe6mAc3LodM5guJJU"]
friends:>> ["PHyUe6mAc3LodM5guJJU"]
Each time a change happens in the database, your addSnapshotListener closure gets called with all data that matches the query - even if that data wasn't change since the last call. This typically means that you'll want to empty self.players at the top of the callback, or loop over the documentChanges collection to determine exactly what changed.
func getFriends() {
// this will empty the players array when ever the get friends function gets called.
self.players.removeAll()
// Initialize the DispatchGroup
let group = DispatchGroup()
// the myFriends array contains documentIDs that I am using to fetch documents from firebase
//
for pid in myFriendObj.myFriends {
group.enter()
_ = Firestore.firestore().collection("Players")
.whereField(FieldPath.documentID(), isEqualTo: pid)
.addSnapshotListener { [self] querySnapshot, error in
if let error = error {
print("Error getting > Players: \(error.localizedDescription)")
return
}
guard let querySnapshot = querySnapshot else { return }
self.players.append(
contentsOf: querySnapshot.documents.compactMap { document in
try? document.data(as: UserProfile.self)
})
group.leave()
}
}
}

Swift dictionary returns nil after setting key and value pair [duplicate]

This question already has an answer here:
Dictionary with values as array on appending one at a time, remains empty
(1 answer)
Closed 3 years ago.
I'm using a table view controller to section users based off of their occupation. I'm starting off with a dictionary and then converting it into a struct to display the different sections.
The dictionary takes a string and array of user objects:
var userByOccupation: [String: [User]] = [:]
I pull the occupations from the backend (firestore), then the user, then I append the user to the specified occupation. However, whenever I set the value & key, then print out the value count from the dictionary, it's returning nil.
Im getting the error in getUsers() function:
(see the last 3 lines that are also marked with their output)
func getOccupations(){
let db = Firestore.firestore()
db.collection("occupations").getDocuments { (snapshot, err) in
if let error = err {
print("There was an error fetching documents: \(error)")
} else {
guard let documents = snapshot?.documents else { return }
for document in documents {
var occupationID = document.documentID
db.collection("occupations").document(occupationID).collection("users").getDocuments(completion: { (secondSnapshot, error) in
if let err = error {
print("There was an error fetching documents: \(err)")
} else {
guard let secondDocuments = secondSnapshot?.documents else { return }
for document in secondDocuments {
self.getUsers(occupationID: occupationID, userID: document.documentID)
}
}
})
}
}
}
}
func getUsers(occupationID: String, userID: String) {
let db = Firestore.firestore()
db.collection("users").document(userID).getDocument(completion: { (snapshot, error) in
if let err = error {
print("There was an error fetching documents: \(err)")
} else {
if let dictionary = snapshot?.data() {
let user = User(dictionary: dictionary as [String: AnyObject])
user.id = snapshot?.documentID
print(occupationID) //MARK - prints: Janitor
print(user.name) //MARK - prints: Jason
self.userByOccupation[occupationID]?.append(user) //MARK: Setting the key & values
print(self.userByOccupation.keys.count) //MARK - prints: nil.
}
}
})
}
Using ? with self.userByOccupation[occupationID] which is nil at first makes the statement affectless
self.userByOccupation[occupationID]?.append(user)
Change to
self.userByOccupation[occupationID] = [user] // or use +=

Load Firestore data to Table view Swift 4

I have a question related to load data from Firestore to table view. Basically, i understand how to do it, but in this case, i kind of confuse if the structure of Firestore as below:
"fund" -> "randomId" -> "agensi: id"
i was able to load the agensi from collection which is "WJ18kBvDLhVrvml2slXB". To get the real agensi name i have to get data from collection "Agensi" as image below:
below is the code that what i have already did:
var agencyname: [String] = []
func getData() {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
// var agencyNumber = 0
for document in querySnapshot!.documents {
let data = document.data()
let agency = data["agensi"] as? String ?? ""
let agencyId = document.documentID
print(agency)
print(agencyId)
//MARK get name
let newdocRef = Firestore.firestore().document("Agensi/\(agency)")
newdocRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let dataDetails = docSnapshot.data()
let agencyNew = dataDetails!["name"] as? String ?? ""
self.agencyname.append(agencyNew)
print("List of the agency: \(self.agencyname.append(agencyNew))")
}
}
self.tableView.reloadData()
}
}
}
i try to print:
self.agencyname.append(agencyNew)
but its display nothing. so, i cannot load the name of the agency into my table view cell. By the way sorry for my bad english. Thanks in advance
There are few things I would like to tell:
1. FireStore queries are asynchronous so there is a callback function when it finishes. Reload your tableView inside the callback instead of end of the loop only then it will load the data properly.
DispatchQueue.main.async {
self.tableView.reloadData()
}
Write above code just below print("List of the agency: \(self.agencyname.append(agencyNew))") line.
2. You're printing "self.agencyname.append(agencyNew)" and it is void by the way so will not print anything so print(\(self.agencyname)) instead.
3. When using a guard in a loop then don't use return because it will break the loop for next iteration if any error occurs. We should use continue here to let the loop execute completely.

Resources