Problem retrieving data from Firestore Database - swift

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

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.

How do i get firestore document id in a generic method?

i am new to swift and i am trying to implement generic method to fetch data from firestore.
func fetch<T: Codable>(query: Query, completion:#escaping (Result<[T]?, Swift.Error>) -> Void) {
var dataArray = [T]()
query.addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
completion(.failure(error!))
return
}
if snapshot.isEmpty {
completion(.success(nil))
return
}
snapshot.documentChanges.forEach { change in
var data: T!
do {
data = try FirestoreDecoder().decode(T.self, from: change.document.data())
} catch let error {
completion(.failure(error))
return
}
if (change.type == .added) {
// print("New data: \(change.document.data())")
dataArray.append(data)
}
}
completion(.success(dataArray))
}
but here i don't know how do i get the document id from change.document.data(). please help me out if any one knows. thank you.
Maybe these links can help
https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/DocumentChange
https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/QueryDocumentSnapshot
You have the property newIndex in change of type UInt or you can print the content of change.document.data() and see if there is a field with a name corresponding to an id. The data method contains the values of a document on your firestore console.
Then you can access the dictionary item as follows:
change.document().data()["id"] as? Int

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