swift wait until array is set before calling function - swift

I would like to call fetchArrayOfUsersBlockingTheCurrentUser() and wait until the array of blocked users is complete before calling fireStoreFetchUsers(), as I would like to dismiss blocked users from the usersarray before loading the users on the screen.
so far the screen loads all users before the array is set and only work when leaving the screen and coming back to the screen
I have tried did set but it calls fetchusers to many times putting the same user multiple times on the screen.
code below:
func fetchArrayOfUsersBlockingTheCurrentUser() {
guard let uid = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).collection("Users Blocking Me")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let d = document.documentID
self.UsersBlockingCurrentUserArray.append(d)
}
}
}
fireStoreFetchUsers()
}
var UsersBlockingCurrentUserArray = [String]()
var users = [User2]()
and the function fireStoreFetchUsers() basically goes;
///fetche users from database
///users.append(user)
//reload data

Don't wait. Move fireStoreFetchUsers() into the completion block.
func fetchArrayOfUsersBlockingTheCurrentUser() {
guard let uid = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).collection("Users Blocking Me")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let d = document.documentID
self.UsersBlockingCurrentUserArray.append(d)
}
self.fireStoreFetchUsers()
}
}
}
And please conform to the naming convention that variable names start with a lowercase letter

Related

firestore fetch subcollection

I'm trying to fetch subcollection of my users document with code below
func readData(){
let userId = Auth.auth().currentUser?.uid
self.db.collection("users/\(userId)/saved").getDocuments { (snapshot, err) in
if let err = err {
print("err")
}
if let userId != nil {
for document in snapshot!.documents {
let docId = document.documentID
let cty = document.get("city") as! String
let ccode = document.get("code") as! String
let countr = document.get("country") as! String
print(cty, ccode, countr,docId)
}
}
}
but my code doesn't print anything, I don't understand the problem, documents exsist, see picture below
You're using illegal syntax with the userId check in the snapshot return but the logic flow is the bigger problem. I would recommend you check if the user is signed in before grabbing the subcollection and checking if there is a viable snapshot instead of checking the state of authentication.
func readData() {
guard let userId = Auth.auth().currentUser?.uid else {
return
}
db.collection("users/\(userId)/saved").getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
return
}
for doc in snapshot.documents {
guard let city = doc.get("city") as? String,
let code = doc.get("code") as? String,
let country = doc.get("country") as? String else {
continue // continue document loop
}
let docId = doc.documentID
print(city, code, country, docId)
}
}
}

How to rewrite the code in order to get rid of the duplicate codes in Swift?

I'm learning ios dev through making an app. I wirte code in Swift and for the backend, I use Firestore. This app is supposed to be the multiplayer tic tac toe.
Since two people access the same data, I store data in Firestore, but every time if I want to check inside of the data, I have to read the same document (which is ok).
In my Viewcontroller, I made several functions that all of them need to get the data from Firestore. So, everytime I have to fetch the same document with same code.
let docRef = db.collection(K.FStore.newGameCollection).document(gameDocumentID)
docRef.addSnapshotListener { (documentSnapshot, error) in
if let err = error {
print("Error getting documents: \(err)")
} else {
let gameDocData = documentSnapshot?.data()....
and some of the examples are
func changePlateImage (plate: UIButton) {
let docRef = db.collection(K.FStore.newGameCollection).document(gameDocumentID)
docRef.addSnapshotListener { (documentSnapshot, error) in
if let err = error {
print("Error getting documents: \(err)")
} else {
let gameDocData = documentSnapshot?.data()
let isPlayer1 = gameDocData?[K.FStore.isPlayer1Turn] as? Bool
let fruitImage = isPlayer1! ? K.Image.apple : K.Image.pineapple
plate.setImage(UIImage(named: fruitImage), for: .normal)
self.changeGameBoard(index: plate.tag, fruit: fruitImage)
}
}
}
and
func changeGameBoard (index: Int, fruit: String){
let docRef = db.collection(K.FStore.newGameCollection).document(gameDocumentID)
docRef.addSnapshotListener { (documentSnapshot, error) in
if let err = error {
print("Error getting documents: \(err)")
} else {
let gameDocData = documentSnapshot?.data()
var gameBoard = gameDocData?[K.FStore.gameBoardField] as? Array<String>
// add name of the fruit in the gameBoard
gameBoard?[index] = fruit
}
}
}
As I said before, every time before I run the functions I write the same code. If I need to write the same code in every function, is there any better way to refactor this function? or I should just make a huge function? Sorry for the stupid question.
What you can do is to write a function to get data.. and then manipulate it according to condition
func getGameDataFromDB (completion: #escaping(_ gameDocData: gameDocDataTypeHere?, _ error:Error?)->Void ) {
let docRef = db.collection(K.FStore.newGameCollection).document(gameDocumentID)
docRef.addSnapshotListener { (documentSnapshot, error) in
if let err = error {
completion(nil,err)
print("Error getting documents: \(err)")
} else {
completion(documentSnapshot?.data(),nil)
}
}
}
Use it like this
getGameDataFromDB { (data, error) in
var gameBoard = data?[K.FStore.gameBoardField] as? Array<String>
// add name of the fruit in the gameBoard
gameBoard?[index] = fruit
}

Fetching particular data of current user from Firestore [Swift]

here you are a screenshot of a result and I would like to grab skills object of current user in firestore into the tableview. Any feedback regarding this?
func getSkills() {
guard (Auth.auth().currentUser?.uid) != nil else {
return
}
self.db.collection("tutors").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let docId = document.documentID
let addedSkills = document.get("skills") as! Array<Any>
print(docId, addedSkills)
}
}
}
}
As mentioned, it seems to be because you are declaring another addedSkills array within the completion block of your query. You should change it to this
func getSkills() {
guard (Auth.auth().currentUser?.uid) != nil else {
return
}
self.db.collection("tutors").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let docId = document.documentID
self.addedSkills = document.get("skills") as! Array<Any> // This is the change
print(docId, addedSkills)
}
}
}
}
The question seems way too generic, I think you can get away with any UITableView tutorial (with networking) out there. However, there are some points you may be aware regardless.
The addedSkills in you example above seems scoped local to the callback. You may remove the let before addedSkills, so compiler can pick up the right reference. Also don't forget to call tableView.reloadData once you have populated the view with new data.
In addition, you may spare force unwrapping things to avoid crashing you app, something like below.
if let documents = querySnapshot?.documents
{
for document in documents
{
if let addedSkills = document.get("skills") as? Array<Any>
{
// Log.
let documentID = document.documentID
print(documentID, addedSkills)
// Update data.
self.addedSkills = addedSkills
// Update UI.
self.tableView.reloadData()
}
}
}
You may want to be more conscious about selecting the right document though, instead of iterating over each. Try setting a breakpoint to your print statement to see the entire (!) documents object.

Unable to fetch data from existing document within subcollection (Swift & Firestore)

Messaging Structure:
messages > currentUserID (document) > partnerID (collection) > message (document)
I can get as far as retrieving the partner ID but I can't retrieve the individual messages (documents) within the collection. Heres the functions Im using:
func observeUserMessages(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let dbRef = db.collection("messages").document(uid).addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot?.documentID else { return }
print("This is the partner ID: \(snapshot)")
self.fetchMessageWithPartnerID(partnerID: snapshot)
}
self.tableView.reloadData()
}
fileprivate func fetchMessageWithPartnerID(partnerID: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
Firestore.firestore().collection("messages").document(uid).collection(partnerID).getDocuments { (snapshot, err) in
print("This is the snapchat count:\(snapshot?.count)")
}
}
Results:
As you can see, it should show the two messages but its not returning anything.
I think there's a difference between .collection() and .document(). Try
Firestore.firestore().collection("messages").collection(uid).collection(partnerID).getDocuments { (snapshot, err) in
print("This is the snapchat count:\(snapshot?.count)")
}

What is the proper way to return a firestore documentID in swift?

I would like to create a function that will return a documentID given a value of a field. I am getting tired of my nested functions. Is there a simple way to have one function that returns the ID ?
Here is what I have been doing. It is an ugly pattern.
public class func getAccountItems(accountName:String) {
let db = Firestore.firestore()
let defaults = UserDefaults.standard
let userId: String! = defaults.string(forKey: "UserUUID")
db.collection("Portfolios")
.whereField("users." + userId, isEqualTo: true)
.limit(to: 1)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
var documentID:String? = nil
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
documentID = document.documentID
if(documentID != nil){
do {
let db = Firestore.firestore()
let defaults = UserDefaults.standard
let portfolioId: String! = defaults.string(forKey: listDocKey)
db.collection("Portfolios").document(portfolioId).collection("Accounts").whereField("name", isEqualTo: accountName)
.getDocuments(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if querySnapshot!.documents.count == 1 {
for document in querySnapshot!.documents {
db.collection("Portfolios").document(documentID!).collection("Accounts").document(document.documentID).collection("Items")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
var items:[INDX01FirestoreService.PortfolioItem] = []
for document in querySnapshot!.documents {
print("\(accountName): \(document.documentID) => \(document.data())")
let pi = try! FirestoreDecoder().decode(INDX01FirestoreService.PortfolioItem.self, from: document.data())
items.append(pi )
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "AccountItemsReceived"), object: nil, userInfo: ["items": items])
}
}
}
}else{
print ("Error count is \(querySnapshot!.documents.count)")
}
}
}
}
}
}
}
}
}
Since Firestore is an async call I either do it this way or I send a notification. I don't like sending notifications all over the place.
Cant post a comment but one thing why do you have so much db references
let db = Firestore.firestore()
this should give you id of the document like you have
documentID = document.documentID
Post here how your data structure looks like and what ids you want to get also have in mind you should store id in each document.
I try to help you.
I decided to just store the documentID in UserDefaults as that handles most of the use cases. Otherwise I do the above, search, find, then process