Firebase Cloud Firestore - Accessing a collection from a reference - swift

High level: In Cloud Firestore, I have two collections. fl_content and fl_files. Within fl_content, I am trying to access fl_files.
Detailed: In fl_content, each document has a field called imageUpload. This is an array of Firebase Document References. (a path to fl_files that I need to access.)
Here's my query for fl_content, in which I am accessing imageUpload reference:
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let property = document.get("imageUpload")
print("PROPERTY \(property!)")
}
}
This prints the following to the console:
PROPERTY Optional(<__NSArrayM 0x60000281d530>(
<FIRDocumentReference: 0x600002826220>
)
)
With this array of Document References, I need to get to fl_files.
This is the part I am having trouble with.
Attempts:
Within the if let statement, I tried accessing fl_files by casting property as a DocumentReference.
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let property = document.get("imageUpload") as? DocumentReference
print("PROPERTY \(property!)")
let test = Firestore.firestore().collection("fl_files").document(property)
}
}
Cannot convert value of type 'DocumentReference?' to expected argument type 'String'
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let property = document.get("imageUpload") as! DocumentReference
let test = Firestore.firestore().collection("fl_files").document(property[0].documentID)
print("TEST \(test)")
}
}
Value of type 'DocumentReference' has no subscripts
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let property = document.get("imageUpload") as! DocumentReference
let test = Firestore.firestore().collection("fl_files").document(property.documentID)
print("TEST \(test)")
}
}
Could not cast value of type '__NSArrayM' (0x7fff87c50980) to 'FIRDocumentReference' (0x10f6d87a8).
2020-02-05 12:55:09.225374-0500 Database 1[87636:7766359] Could not cast value of type '__NSArrayM' (0x7fff87c50980) to 'FIRDocumentReference' (0x10f6d87a8).
Getting closer!
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
docRef.getDocument(completion: { document, error in
if let err = error {
print(err.localizedDescription)
return
}
let imageUpload = document?["imageUpload"] as? NSArray ?? [""]
print("First Object \(imageUpload.firstObject!)")
})
This prints: First Object <FIRDocumentReference: 0x600001a4f0c0>
Here are two screenshots to help illustrate what the Firestore database looks like..
Ultimately, I need to get to the file field within fl_files. How do I access this from the imageUpload DocumentReference?

Finally got it, thanks to the help of #Jay and #Emil Gi.
The "A-HA" moment came from Emil Gi's comment: All I can say is that if you successfully get element of DocumentReference type, then it must have an id property, which you can extract and query collection by document id.
let imageUploadReference = item.imageUpload.first as? DocumentReference
let docRef = Firestore.firestore().collection("fl_files").document(imageUploadReference!.documentID)
docRef.getDocument(completion: { document, error in
if let err = error {
print(err.localizedDescription)
return
}
let fileNameField = document?.get("file") as! String
print("File name from fl_files \(fileNameField)")
})
Once I finally had access to the corresponding "file", it was very simple to download the full URL of the image to the imageView.
I appreciate all of your help!!!

Here's some sample code that shows how to read and print out any of the fields values and also how to read the imageUpload field (an array) and print out the first element's value.
I've included both ways to read data from a document because I believe it answers both parts of the question: how to get the array field imageUpload and then access the first element within that array and how to get the value of the file field within fl_files.
Assuming this points to the correct document:
let docRef = Firestore.firestore().collection("fl_content").document(item.id)
this code will read two fields from the document being pointed to: imageUpload (an array) and fl_id (a String)
docRef.getDocument(completion: { document, error in
if let err = error {
print(err.localizedDescription)
return
}
print( document!.data() ) //for testing to see if we are getting the fields
//how to read an array and access it's elements
let imageUploadArray = document?["imageUpload"] as? Array ?? [""]
if let url = imageUploadArray.first {
print(url)
}
//this is also valid for reading the imageUpload field array
//let anArray = document?.get("imageUpload") as? Array ?? [""]
//if let url = anArray {
// print(url)
//}
//how to read a single field, like file within fl_files
let someId = document?.get("fl_id") as! String //example of reading a field
print(someId)
})
Edit:
It appears the objects stored in the imageUpload array are references not strings. As an example of how to access them... here's the code
let refArray = document?.get("imageUpload") as? [DocumentReference] ?? []
for docRef in refArray {
print(docRef.path) //prints the path to the document at this ref
}

Related

Get the Data of any user according to ID

so, here how can i get the data of any user according to uids ?
From your screenshot we can see that the value of the field uid is also used as the ID of the user's Firestore document (which is a very good approach :-))
Therefore you can simply query the document of a specific user as follows:
let docRef = db.collection("users").document(uid) // We use the uid to define the Document Reference
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
} else {
print("Document does not exist")
}
}

How can I tell if I value already exists in Firebase Firestore?

I would like to receive a bool letting me know if my document has a Wasiyyah or not..
What I've tried:
Firestore.firestore().collection(user!.uid).document(docID).value(forKey: "Wasiyyah")
Which only crashes every time, so there must be something I'm not understanding here.
There isn't any function to check if a field exists in a document. You'll have to fetch the document and check for it's existence:
let docRef = Firestore.firestore().collection(user!.uid).document(docID)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = document.data()
// Check if the field exists in data
} else {
print("Document does not exist")
}
}

Struggling To Query Using getDocuments() in Firestore Swift

This is the first time I am using a Firestore Query and I'm struggling to parse the data. I normally use the same setup when I get documents (which works), but when I attach it to a query it does not work.
I am trying to query the database for the shop most visited, so I can later set it as favourite.
My Code:
func findFavouriteShop(completed: #escaping ([String]) -> Void)
{
// Variables
let dispatch = DispatchGroup()
var dummyDetails = [String]()
// References
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let groupCollectionRef = String("visits-" + userID! )
// Query the database for the document with the most counts
dispatch.enter()
db.collectionGroup(groupCollectionRef).order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
print(snapshot)
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
// Start Assignments
let shopName = data["shopName"] as? String
let count = data["count"] as? String
// Append the dummy array
dummyDetails.append(shopName!)
dummyDetails.append(count!)
}
dispatch.leave()
}
dispatch.notify(queue: .main, execute: {
print("USER number of documents appended: \(dummyDetails.count)")
completed(dummyDetails)}
)
}
Using Print statements it seems as if the guard statement kicks the function out. The processor does not reach the for-loop to do the assignments. When I print the snapshot it returns an empty array.
I am sure I have used the wrong notation, but I'm just not sure where.
There's a lot to comment on, such as your choice of collection groups over collections (maybe that's what you need), why you limit the results to one document but feel the need to query a collection, the naming of your collections (seems odd), the query to get multiple shops but creating a function that only returns a single shop, using a string for a count property that should probably be an integer, and using a string array to return multiple components of a single shop instead of using a custom type.
That said, I think this should get you in the right direction. I've created a custom type to show you how I'd start this process but there's a lot more work to be done to get this where you need it to be. But this is a good starting point. Also, there was no need for a dispatch group since you weren't doing any additional async work in the document parsing.
class Shop {
let name: String // constant
var count: Int // variable
init(name: String, count: Int) {
self.name = name
self.count = count
}
}
func findFavouriteShops(completion: #escaping (_ shops: [Shop]?) -> Void) {
guard let userID = Auth.auth().currentUser?.uid else {
completion(nil)
return
}
var temp = [Shop]()
Firestore.firestore().collection("visits-\(userID)").order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
completion(nil)
return
}
for doc in snapshot.documents {
if let name = doc.get("shopName") as? String,
let count = doc.get("count") as? String {
let shop = Shop(name: name, count: count)
temp.append(Shop)
}
}
completion(temp)
}
}
You can return a Result type in this completion handler but for this example I opted for an optional array of Shop types (just to demonstrate flexibility). If the method returns nil then there was an error, otherwise there are either shops in the array or there aren't. I also don't know if you're looking for a single shop or multiple shops because in some of your code it appeared you wanted one and in other parts of your code it appeared you wanted multiple.
findFavouriteShops { (shops) in
if let shops = shops {
if shops.isEmpty {
print("no error but no shops found")
} else {
print("shops found")
}
} else {
print("error")
}
}

Swift + Firebase. Accessing current user's document

My current firebase structure is Collection of Users which then have a subcollection of habits. For a given user, I want them to be able to add to their own collection of routines. however, running into an issue. When I run the function below, it just creates a separate user with a separate routine. How would I tie a new routine to a current authenticated user?
func addData(routineMsg: String){
let db = Firestore.firestore()
let user = db.collection("users").document()
let routine = db.collection("users").document("GzsHAHq1P0uXGdlYwF8P").collection("routines").document()
routine.setData(["id": routine.documentID, "routine": routineMsg]) { err in
if err != nil {
print((err?.localizedDescription)!)
return
}
}
}
Right now, the code shows how I hard-code it to a certain document (GzsHAHq1P0uXGdlYwF8P), but would like to be able to determine the document dynamically by user
let user = db.collection("users").document()
By not passing document() an argument, what you are doing is creating a new document reference with an auto-generated document ID. What you want to do is pass the method with a string that locates the user's document. Ideally, this would be the user's ID:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let userDocRef = db.collection("users").document(uid)
From there, to generate random document IDs in the subcollection, do what you were doing before:
func addData(routineMsg: String) {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let db = Firestore.firestore()
let userDocRef = db.collection("users").document(uid)
let routineDocRef = userDocRef.collection("routines").document()
routineDocRef.setData([
"id": routineDocRef.documentID,
"routine": routineMsg
]) { error in
if let error = error {
print(error)
}
}
}

Cannot subscript a value of type 'String' with an index of type 'String' Swift Error

First of all, the other questions isn't the same as mine. I'm trying to get a single value from the data in Swift Xcode after retrieving the data from Firebase Firestore. Instead, it gives an error:
Cannot subscript a value of type 'String' with an index of type 'String'
ref.getDocument { (document, err) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing: )) ?? "nil"
print ("Cached document data: \(dataDescription)")
profilename?.text = dataDescription["name"] as! String
} else {
print ("Document doesn't exist")
}
}
The error occurs at the
profilename?.text = dataDescription["name"] as! String
line.
The print of dataDescription is:
Cached document data: ["email": test2#gmail.com, "name": Test2, "phone": 408-222-2222]
Can anyone help me on this?
Let me address the question via an example. Suppose you have a FireStore with a users collection, and each document has a documentID of the users uid and stores their name within a field. It would look like this
users
uid_1
name: "Henry"
and we want to access the name field for this user. Here's the code that reads that document, prints the uid and name.
func readUsersName() {
let users = self.db.collection("users")
let thisUser = users.document("uid_1")
thisUser.getDocument { documentSnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
let docId = documentSnapshot?.documentID
let name = documentSnapshot?.get("name") as! String
print(docId, name)
}
}