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

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

Related

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: Unable to append struct instance to array

I have been having some trouble creating a temporary array of user data from Firestore. Basically I created a function that retrieves user data from a Firestore collection and then iterates through each document within that collection, creating an instance of my "Thought" struct for each one. I then append each "Thought" instance to a temporary array called "tempThoughts", and the function then returns that array. The problem is that nothing seems to be appended to the array in the function. When I test it by printing out the contents of the array upon completion, it just prints an empty array.
The data itself is being read from the Firestore collection as it prints out each document the function iterates through, so I don't think that is the problem. I also tried checking to see if I am actually creating instances of the "Thought" struct properly by printing that out, and that seemed to be working. Does anyone have any idea what's wrong with the way I am appending the struct instances to the array? Perhaps there is a better way to go about doing this? Thanks for any help in advance.
Here is my current function:
func getUserDocuments() -> [Thought]{
var tempThoughts = [Thought]()
db.collection(cUser!.uid).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let tempThought: Thought = Thought(id: document.get("id") as! String, content: document.get("content") as! String, dateCreated: document.get("timestamp") as! String, isFavorite: (document.get("isFavorite") != nil))
tempThoughts.append(tempThought)
}
}
}
print("TEST")
print(tempThoughts)
return tempThoughts
}
Your getDocuments is an asynchronous operation. And you've updated your tempThoughts in it's completion only. But the place where you've printed it out will get executed before the getDocuments completion. Check out the order of results logged in the console.
You need to update your code like this
func getUserDocuments(_ onSuccess: ((_ thoughts: [Thought] ) -> Void), onFailuer: ((_ error: String) -> Void)) {
var tempThoughts = [Thought]()
db.collection(cUser!.uid).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
onFailuer(err)
} else {
DispatchQueue.main.async {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let tempThought: Thought = Thought(id: document.get("id") as! String, content: document.get("content") as! String, dateCreated: document.get("timestamp") as! String, isFavorite: (document.get("isFavorite") != nil))
tempThoughts.append(tempThought)
}
print("TEST")
print(tempThoughts)
onSuccess(tempThoughts)
}
}
}
}
user this code
And you can use this function like this
getUserDocuments({ (thoughts) in
// Your logic
}) { (error) in
// error Occured
}

Multiple Firestore snapshots overwrite each other

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)

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 +=

Resources