Swift + Firebase. Accessing current user's document - swift

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)
}
}
}

Related

How can I reference second level collections in Firebase?

Currently my Firebase database is using the following structure
Users
User 1 Data
User 2 Data...
However I have created a new collection inside of the "users" collection called "products" which will store product details that users have uploaded on the application.
How can I ensure that once a user uploads a new product, it is only uploaded into their 'User X data' dataset, inside the respective "products" collection. The code I currently have only uploads the data into the "users" collection, with no reference of the users who added the product as required. I have shown the structure of my Firebase database below for reference.
Here is my code:
let user = Auth.auth().currentUser
db.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let userId = data["uid"] as! String
if userId == user?.uid {
db.collection("users").document("products").setData(["productNameField":firstName, "productURLField":lastName,"productPriceField":ebayName, "productDescriptionField":etsyName, "productTimeRemainingField":email])
}
}
}
}
How would I go about updating my code to achieve this?
I think you're looking for
let user = Auth.auth().currentUser
db.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let userId = data["uid"] as! String
if userId == user?.uid {
document.reference.collection("products").addDocument(["productNameField":firstName, "productURLField":lastName,"productPriceField":ebayName, "productDescriptionField":etsyName, "productTimeRemainingField":email])
}
}
}
}
So here document is the DocumentSnapshot that you're looping over, so document.reference gives you the reference to that specific document, and document.reference.collection("products") then points to the `products subcollection for that specific document.
You're wastefully looping over the entire list of users to FIND the user document needed. Simplify, simplify - use the user.uid as the Id of the user document!! (i.e. when you create the user document, save it as
db.collection("users").doc(user.uid).set({whatever})
...then it's trivial to access as...
let user = Auth.auth().currentUser
db
.collection("users")
.doc(user.uid)
.collection("products")
.setData([
"productNameField":firstName,
"productURLField":lastName,
"productPriceField":ebayName,
"productDescriptionField":etsyName,
"productTimeRemainingField":email
]);
If there is another reason to keep the docID and the uid separate (not shown here), then use a query to get the SPECIFIC document with the uid, rather than downloading ALL of them
let user = Auth.auth().currentUser
db
.collection("users")
.where(field: "uid", opStr:"==", value: user.uid)
.getDocuments( { (snapshot, error) in
if let error = error {
print(error)
return
} else { //the query should only return the one document, but queries always return an array
snapshot
.documents[0]
.ref()
.document("products")
.setData([
"productNameField":firstName,
"productURLField":lastName,
"productPriceField":ebayName,
"productDescriptionField":etsyName,
"productTimeRemainingField":email
])
}
}
...etc...
I don't generally use Swift, so the where clause may require different formatting, but the idea is GET ONLY THE DOCUMENT YOU NEED. Your security rules should only be allowing this, anyway.

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")
}
}

refrence to document ID

#IBAction func NextButtonTapped(_ sender: Any) {
//validate the fileds
let Error = validateFields()
if Error != nil {
// there is somthing wrong with the fields show error message
showError(Error!)
}
else {
// create cleaned versions of the data
let Password = PasswordTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Email = EmailTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Firstname = FirstnameTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Lastname = LastnameTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Age = AgeTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
// create the user
Auth.auth().createUser(withEmail: Email, password: Password) {
(results, Err) in
// check for errors
if Err != nil {
// there was an error creating the user
self.showError("Error creating user")
}
else {
// user was created succesfully store user info
let db = Firestore.firestore()
db.collection("users").document(results!.user.uid).setData(["first
name":Firstname, "last name":Lastname, "age":Age,
"uid":results!.user.uid]) { (Error) in
if Error != nil {
// show error message
self.showError("error saving user data")
}
}
//transition to the home screen
self.transitionToHome()
}
}
}
}
So basically here I am authenticating the user on firebase. (I made
the document ID = the user ID) then I am entering the users info
into the firebase database into a document where its ID is also
equal to the users ID from when they are authenticated. Now what I
am trying to do is to create or get a reference to the document ID
where some of the users info is already stored like name, last name,
age... so I can later add/merge more info into that same document
the name, last name and age is stored under. This is how I am trying
to merge the info together in a diffrent view controller
db.collection("users").document(*******).setData(["middle
Name":Middlename, "favourite colour":Favouritecolour], merge: true)
{ (Error) in
if Error != nil {
// show error messgae
self.showError("error saving user data")
}
}
Where I put "*******" is where I am supposed to reference the
document ID so I can merge/add this info into the same document as
the other users information where the name, last name and age is
stored.
The code I showed you and asked about earlier on how to get a
document ID from, was code I found on stack overflow where they guy
had a similar problem as mine. He was trying to access something out
of his document but I am just trying to create a reference to the
document ID.
The code form earlier:
func getDocument() {
//get specific document from current user
let docRef =
Firestore.firestore().collection("users").document(Auth.auth().currentUs er?.uid ?? "")
//get data
docRef.getDocument { (document, Error) in
if let document = document, document.exists {
let dataDescription = document.data()
print(dataDescription?["uid"])
} else {
print("Document does not exist")
}
}
}
But I don't know if this code will help me, I just thought it might
because its also accessing the document, thats why I though it might
be my answer to getting the document ID.
So basically my question is what do I need to do wether its adding
to the code I found on this site, or if I have to write my own code,
so I can get a reference to the Document ID where my name, last name
and Age is stored, so I can merge more Info into that document
Thank You Very Much!!!
To print the document ID do something like this:
let docRef = Firestore.firestore().collection("users").document(Auth.auth().currentUser?.uid ?? "")
// Get data
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data()
print(dataDescription?["firstname"])
print(document.documentID)
} else {
print("Document does not exist")
}
}
I highly recommend spending some time in the Firestore documentation, as this is covered in there under getting multiple documents from a collection

Can I use a Swift String Variable to get a Firebase document for a user?

I am trying to read some data from my database but I only want to read the data for one user instead reading the data from all Users.
I tried using the variable userEmail to list only a certain users code.
let userEmail = String((Auth.auth().currentUser?.email)!)
func readArray() {
print(userEmail)
let docRef = Firestore.firestore().collection("users").document("\(userEmail)")
//let docRef = db.collection("cities").document("SF")
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")
}
}
}
I can get the user's information if I type out their user email as "test#gmail.com" but I know this won't work for other users with different login emails.
enter image description here
The issue fixed itself. Just had to reset my internet connection. This is very odd but it turns out that you can use variables to find your specific document.

Function is not waiting to adjust boolean values (by checking a field in Firestore) and returns default values before

I'm trying to return a list of booleans to check in what registration step the user is and if it has registered through Facebook or Phone. The function I made gets the user doc and checks for two fields to see if they exist (interest and id). id, because only FB users have that field and interests because that is the last step of the registration process. It appears the function is returning the default values [true,true] before I even get the chance to modify the values.
fileprivate func checkRegistrationComplete(_ uid: String?) -> [Bool] {
//I will return a list with the first bool to know if it has filled all the info and the second if it has done so through FB or phone
var userRegistered = true
var userRegisteredFB = true
let db = Firestore.firestore()
let docRef = db.collection("users").document(uid!)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data()
userRegistered = dataDescription?["interest"] != nil
userRegisteredFB = dataDescription?["id"] != nil
print(userRegisteredFB)
print(userRegistered)
} else {
print("Document does not exist")
}
}
print(userRegisteredFB,userRegistered)
return [userRegistered,userRegisteredFB]
}
The output should be [false,true] but it returns [true,true]. And the print functions show that the get method is actually working but it shows after the return call