Value of type 'StorageMetadata' has no member 'downloadURL'

@coder3000

So what you are saying is that for every user that you save in your users collection, you want to have the documentID to be the currentUser.uid. Is that correct?

Yes sir. Each nickname that is submitted should be specific to the user.

Are you expecting the nickname to be unique across all users?

yes sir.

That means you are going to have to loop through all the users and make sure that the nickname does not already exist.

That’s interesting. A foreach loop? :thinking:

Foreach user, if the nickname equals nickname… Error. “Nickame already exists”?

@coder3000

It’s not that simple. You have to select all the documents from the users collection in a snapshot and loop through all the snapshot.documents to see if the nickname already exists.

Firebase is not simple. It’s very awkward at times and often requires a lot of careful coding.

1 Like

@coder3000

Something else to consider is that if you are opting to allow the user to add a nickname later on, the search through all the user documents needs to take into account the property may not exist for some records.

I think that makes it a bit awkward so my suggestion is that when you finally get your App finished, the user record needs to have all properties created at the time the user signs up even though they may be blank.

When you search through all the user records to determine if a nickname has already been used, you would ignore fields that are blank.

Do you see what I am getting at?

1 Like

Yes. So I’ll add the fields that I need even though I don’t present it at the beginning of the SignUp process. This way the user can access them later since it is optional. I believe the code that was writing earlier was querying a field that wasn’t there. I will try that approach.

@coder3000

I’ve been tinkering with code to save a nickname when the user Logs In (ie, already a user) and it works nicely.

Essentially I have this function:

func addNickname(nickname: String) {
        let cleanedNickname = nickname.trimmingCharacters(in: .whitespacesAndNewlines)

        // Check if nickname is unique
        isNicknameUnique(nickname: cleanedNickname) { isUnique in
            if isUnique {
                let db = Firestore.firestore()

                // Where users document ID is set to currentUser.uid
//                let user = db.collection("users").document("\(Auth.auth().currentUser?.uid ?? ""))")
//                user.setData(["nickname": cleanedNickname], merge: true)

                // OR

                // Where users document ID is automatically created.
                let users = db.collection("users")
                let query = users.whereField("uid", in: [Auth.auth().currentUser?.uid ?? ""])

                query.getDocuments { snapshot, error in
                    if let error = error {
                        self.showError(message: error.localizedDescription)
                    } else if let snapshot = snapshot {
                        for doc in snapshot.documents {
                            let user = db.collection("users").document("\(doc.documentID)")
                            user.setData(["nickname": cleanedNickname], merge: true)
                            self.showError(message: "Nickname saved.")
                        }
                    }
                }
            } else {
                self.showError(message: "Nickname is already in use.")
            }
        }
    }

which calls this function to test that the nickname supplied is unique.

    func isNicknameUnique(nickname: String, completion: @escaping (Bool) -> Void) {
        let db = Firestore.firestore()

        // Set flag to true initially
        var isUnique = true
        let currentUserId = Auth.auth().currentUser?.uid ?? ""

        let users = db.collection("users")
        users.getDocuments { snapshot, error in
            guard error == nil else {
                self.showError(message: "Error retrieving records. Try again later.")
                return
            }
            if let snapshot = snapshot {
                for doc in snapshot.documents {
                    if let thisNickname = doc["nickname"] as? String, currentUserId != doc["uid"] as? String  {
                        if thisNickname == nickname {
                            // Set flag to false
                            isUnique = false
                            break
                        }
                    }
                }
            }
            completion(isUnique)
        }
    }

and is called from within the loginTapped Button code after login has been successful.

    @IBAction func loginTapped(_ sender: Any) {

        // Clear and hide the errorlabel
        errorLabel.alpha = 0
        errorLabel.text = ""

        loginUser { success in
            if success {
                self.getProfileImage { returnedImage in
                    if let image = returnedImage {
                        self.logoImageView.image = image
                        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                            self.transitionToHome()
                        }
                    }
                }
                if let nickname = self.nicknameTextField.text, self.nicknameTextField.text != "" {
                    self.addNickname(nickname: nickname)
                }
            }
        }
    }

The only thing is that the way I’ve written it, you only get one shot at setting the nickname on each login but the concept works.

Ideally you would probably add the nickname somewhere else in your App (say, in a Profile settings screen), by presenting a textField and a “Save Nickname” button so that you could continue to give the user a chance to figure out a nickname that is Unique and eventually get an acknowledgement that the nickname was saved.

1 Like

I have the error popping up. What is your suggestion: Value of type ‘NickName’ has no member ‘showError’


@coder3000

Copy the showError function that is in the SignUpViewController and paste that in the current View Controller.

1 Like

The only error left is: Cannot find ‘loginUser’ in scope

@coder3000

I better check with you that you are putting this code in your LoginViewController?

Oh ok. So all 3 are meant to LoginViewController? I have put it in a NicknameViewController, thinking that the user could set it up there. I miss understood. Also I am assuming that I need to add and extra field for the Nickname to be entered along side the email and password, correct? I just added the code to LoginViewController, but still has that error:

import UIKit
import FirebaseAuth
import FirebaseCore
import AVFoundation
import FirebaseFirestore

class LoginViewController: UIViewController {

    @IBOutlet var emailTextField: UITextField!
    @IBOutlet var passwordTextField: UITextField!
    @IBOutlet var loginButton: UIButton!
    @IBOutlet var errorLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        setUpElements()

        }
        
    func setUpElements() {
        
        // Hide error label
        errorLabel.alpha = 0
        
        // Style the elements
        Utilities.styleTextField(emailTextField)
        Utilities.styleTextField(passwordTextField)
        Utilities.styleFilledButton(loginButton)
        
    }
    
    // 7. There's something wrong with the fields, show error message
    func showError(message:String) {
        
        errorLabel.text = message
        errorLabel.alpha = 1
    }
    
   @IBAction func loginTapped(_ sender: Any) {
       
       // 1. Validate Textfields
       
       // 3. Create Cleaned verson of the Textfield
       let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
       let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
       
       // 2. Signing in user
       Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
           
           if error != nil {
               // 4. Couldn't sign in
               self.errorLabel.text = error!.localizedDescription
               self.errorLabel.alpha = 1
           }
           else {
               loginUser { success in
                           if success {
                               self.getProfileImage { returnedImage in
                                   if let image = returnedImage {
                                       self.logoImageView.image = image
                                       DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                                           self.transitionToHome()
                                       }
                                   }
                               }
                               if let nickname = self.nicknameTextField.text, self.nicknameTextField.text != "" {
                                   self.addNickname(nickname: nickname)
                               }
                           }
                       }
                   
               // 5. Transition to homescreen
               
               let profileVC = UIStoryboard(name: "Main", bundle: nil)
               let controller = profileVC.instantiateViewController(identifier: "tasks") as! TasksViewController
               
               controller.modalPresentationStyle = .fullScreen
               //present(controller , animated: true)
               
               self.view.window?.rootViewController = UINavigationController(rootViewController: controller)
               self.view.window?.makeKeyAndVisible()
           }
       }
        
    }

    func addNickname(nickname: String) {
            let cleanedNickname = nickname.trimmingCharacters(in: .whitespacesAndNewlines)

            // Check if nickname is unique
            isNicknameUnique(nickname: cleanedNickname) { isUnique in
                if isUnique {
                    let db = Firestore.firestore()

                    // Where users document ID is set to currentUser.uid
    //                let user = db.collection("users").document("\(Auth.auth().currentUser?.uid ?? ""))")
    //                user.setData(["nickname": cleanedNickname], merge: true)

                    // OR

                    // Where users document ID is automatically created.
                    let users = db.collection("users")
                    let query = users.whereField("uid", in: [Auth.auth().currentUser?.uid ?? ""])

                    query.getDocuments { snapshot, error in
                        if let error = error {
                            self.showError(message: error.localizedDescription)
                        } else if let snapshot = snapshot {
                            for doc in snapshot.documents {
                                let user = db.collection("users").document("\(doc.documentID)")
                                user.setData(["nickname": cleanedNickname], merge: true)
                                self.showError(message: "Nickname saved.")
                            }
                        }
                    }
                } else {
                    self.showError(message: "Nickname is already in use.")
                }
            }
        }
    
    func isNicknameUnique(nickname: String, completion: @escaping (Bool) -> Void) {
            let db = Firestore.firestore()

            // Set flag to true initially
            var isUnique = true
            let currentUserId = Auth.auth().currentUser?.uid ?? ""

            let users = db.collection("users")
            users.getDocuments { snapshot, error in
                guard error == nil else {
                    self.showError(message: "Error retrieving records. Try again later.")
                    return
                }
                if let snapshot = snapshot {
                    for doc in snapshot.documents {
                        if let thisNickname = doc["nickname"] as? String, currentUserId != doc["uid"] as? String  {
                            if thisNickname == nickname {
                                // Set flag to false
                                isUnique = false
                                break
                            }
                        }
                    }
                }
                completion(isUnique)
            }
        }

}
    
    


@coder3000

In my case I put the code in the LoginViewController just for the purposes of testing that the code works.

If you had intended to have a NicknameViewController then that’s fine. Do all the nickname stuff in there and leave the LoginViewController just for the purposes of enabling the user to log in.

Perfect. Also… It seem as though the code wants to display the user’s profile pic …the part where getProfileImage is located. Correct? And, The only error left is: Cannot find ‘loginUser’ in scope. What am I missing?

@Chris_Parker Nevermind… I made quick edits that made it work great. Thanks again!

1 Like

A post was split to a new topic: Firebase accessing sub-collection