Fetching particular data of current user from Firestore [Swift] - 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.

Related

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
}

Cannot Return an array created within a function Swift

I have an odd issue and I’m not sure what I am doing wrong.
I have the following function that I want called in viewDidLoad to load all documents in a collection from Firestore that will be displayed in a tableview.
func functionName() -> [String] {
var NameArray = [String]()
db.collection("Names").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
NameArray.append("\(document.documentID)")
}
}
}
print(NameArray)
print(NameArray.count)
return (NameArray)
}
The function throws a warning result is being ignored. I don’t want to silence it as I need the value, it should return an array with the document names.
When I tried the below code, it returns the array and count as expected.
#IBAction func fetchUsersButton(_ sender: UIButton) {
var NameArray = [String]()
db.collection("Names").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
NameArray.append("\(document.documentID)")
}
}
print(NameArray)
print(NameArray.count)
}
}
However, I need to be able to return the array created so it can be used outside the function. Is anyone able to help?
Instead of returning an array you need to place it in a completion handler.
func functionName(_ completion: #escaping ([String]) -> Void) {
var NameArray = [String]()
db.collection("Names").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
NameArray.append("\(document.documentID)")
}
}
completion(NameArray)
}
}
The reason that you aren't returning anything is because db.collection().getDocuments is an asynchronous function. What this means is that the function gets to "return" before the db.collection().getDocuments code is done executing.
"Return" assumes that the code will execute synchronously which means it will execute line by line in a predictable order. An asynchronous function is one in which we don't know when it will finish executing (which is always true of network code).
What if the network is down? What if it takes a long time to download? Since we can't know, we use a completion handler to "return" what we need once the function has completed. The other suggestions are great, below is another solution. As a note, it assumes that this function is part of class, and you want to assign the result to an outside variable.
class MyClass {
var myNameArray = [String]()
func functionName() {
db.collection("Names").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
myNameArray.append("\(document.documentID)")
}
}
}
}
}
Another small thing about naming conventions, variables should utilize camelCase, so nameArray is preferable to NameArray.

Problem retrieving data from Firestore Database

I am trying to retrieve data from a collection in my firestore database. But when print the retrieved data, it doesn't conform to the format I expected.
Here is my code to retrieve the data below :
let db = Firestore.firestore()
func loadEvents() -> [Event] {
var events = [Event]()
let decoder = JSONDecoder()
db.collection("Events").addSnapshotListener { QuerySnapshot, error in
guard let documents = QuerySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
for doc in documents {
let dict = doc.data()
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
let event = try? decoder.decode(Event.self, from: data)
events.append(event!)
print(events) // Prints stuff like [Envy.Event] ...
}
}
// let events = documents.map {$0.data()}
}
return events
}
On reaching the print(events) it prints stuff like [Envy.Event]
[Envy.Event, Envy.Event]
[Envy.Event, Envy.Event, Envy.Event]
[Envy.Event, Envy.Event, Envy.Event, Envy.Event] which is not suitable data format for manipulation. I don't even understand this format; Can someone help me out ?
you are printing it wrong . you are printing the whole model class you created you need to refer the object inside the class for eg.
print(events.name) // use the object name you have in your class
print(events.location) // use the object name you have in your class
To add the data into array
Example Code:
db.collection("Events").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let list = Event(name: document.get("yourKeyName") as? String,location: document.get("yourAnotherKeyName") as? String)
events.append(list)
print(event.name)
print(event.location)
}
}
}
Hope this helps

swift wait until array is set before calling function

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

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