3

I'm a bit of a newb here, so please be kind. I'm a former Air Force pilot and am currently in law school, so coding is not my full time gig...but I'm trying to learn as I go (as well as help my kiddos learn).

I'm working on a profile page for my iOS app. I've gone through the firebase documentation quite extensively, but it just doesn't detail what I'm trying to do here. I've also searched on this site trying to find an answer...I found something that really helped, but I feel like something is just not quite right. I posted this previously, but I deleted because I did not receive any helpful input.

What I'm trying to do is display the user's data (first name, last name, phone, address, etc.) via labels. The code (provided below) works to show the user id and email...I'm thinking this is because it is pulled from the authentication, and not from the "users" collection. This code is attempting to pull the rest of the user's data from their respective document in the users collection.

Here is the full code for the viewController. I've tried and failed at this so many times that I'm really on my last straw...hard stuck! Please help!

My guess is that something is not right with the firstName variable...whether that be something wrong with the preceding database snapshot, or with the actual coding of the variable. But then again...I don't know what I'm doing...so perhaps I'm way off on what the issue is.

//  ClientDataViewController.swift

import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore



class ClientDataViewController: UIViewController {

   
   
    
    
    @IBOutlet weak var firstNameLabel: UILabel!
    @IBOutlet weak var lastNameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var phoneLabel: UILabel!
    @IBOutlet weak var streetLabel: UILabel!
    @IBOutlet weak var street2Label: UILabel!
    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var stateLabel: UILabel!
    @IBOutlet weak var zipLabel: UILabel!
    @IBOutlet weak var attorneyLabel: UILabel!
    @IBOutlet weak var updateButton: UIButton!
    @IBOutlet weak var passwordButton: UIButton!
    @IBOutlet weak var uidLabel: UILabel!
    
    
    
    
   let id = Auth.auth().currentUser!.uid
    let email = Auth.auth().currentUser!.email
    
   
  
   // MARK: Lifecycle
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            self.uidLabel.text = id
            self.emailLabel.text = email
            
        }
        
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated) // call super
            
            getName { (name) in
                if let name = name {
                    self.firstNameLabel.text = name
                    print("great success")
                }
            }
        }
        
        // MARK: Methods
        
        func getName(completion: @escaping (_ name: String?) -> Void) {
            let uid = "dL27eCBT70C4hURGqV7P"
            let docRef = Firestore.firestore().collection("users").document(uid)

            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")
                }
                completion("put the first name data here after we figure out what's in the doc")
            }
        }
            }

Matt Mixon
  • 51
  • 8
  • I think you're on the right track, but I don't see where you invoke the the `getName` function. Have you tried calling it from viewDidLoad? (a better choice might be viewWillAppear, but both should work) – danh Aug 15 '20 at 05:20
  • Thanks for the input...I'll give that a try! – Matt Mixon Aug 15 '20 at 05:26
  • @danh I've added that to the code....I'm not receiving any errors, but the label is not updating – Matt Mixon Aug 15 '20 at 05:53
  • I can think a couple things to try. First, this won't fix it, but be sure to call super at the top of the view lifecycle hooks (ie super.viewWillAppear()). Next, what prints on that print statement in viewWillAppear? That's going to tell us a lot! Another debug step: set the firstNameLabel text to prove that the outlet is connected, e.g. in viewDidLoad, `self.firstNameLabel.text = "I should be replaced by data from the server"` – danh Aug 15 '20 at 14:34
  • @danh thanks! The label in viewDidLoad upadated to the "I should be replaced by data from the server", so at least the outlet is connected appropriately. – Matt Mixon Aug 16 '20 at 00:00
  • danh, I've reposted the code just now exactly "as is" currently in my project. I've done some playing around with the "firstname", but I believe what I have now posted as the code to be correct. The firebase collection is named "users" (all lowercase), and the field inside each "users" document is "firstname" (all lowercase). – Matt Mixon Aug 16 '20 at 01:51

2 Answers2

4

The following with solve your problems. However, I'd advise against declaring id and email as force-unwrapped instance properties; they don't even need to be instance properties, let alone force unwrapped. Always safely unwrap optionals before using their values, especially these authorization properties because if the user isn't signed in or is signed out underneath you (expired token, for example), the app would crash here and, as with flying planes, crashing is always to be avoided.

class ClientDataViewController: UIViewController {
    @IBOutlet weak var firstNameLabel: UILabel!
    @IBOutlet weak var lastNameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var phoneLabel: UILabel!
    @IBOutlet weak var streetLabel: UILabel!
    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var stateLabel: UILabel!
    @IBOutlet weak var zipLabel: UILabel!
    @IBOutlet weak var attorneyLabel: UILabel!
    @IBOutlet weak var updateButton: UIButton!
    @IBOutlet weak var passwordButton: UIButton!
    @IBOutlet weak var uidLabel: UILabel!
    
    let id = Auth.auth().currentUser!.uid
    let email = Auth.auth().currentUser!.email
    
    // MARK: Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.uidLabel.text = id
        self.emailLabel.text = email
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated) // call super
        
        getName { (name) in
            if let name = name {
                self.firstNameLabel.text = name
                print("great success")
            }
        }
    }
    
    // MARK: Methods
    
    func getName(completion: @escaping (_ name: String?) -> Void) {
        guard let uid = Auth.auth().currentUser?.uid else { // safely unwrap the uid; avoid force unwrapping with !
            completion(nil) // user is not logged in; return nil
            return
        }
        Firestore.firestore().collection("users").document(uid).getDocument { (docSnapshot, error) in
            if let doc = docSnapshot {
                if let name = doc.get("firstName") as? String {
                    completion(name) // success; return name
                } else {
                    print("error getting field")
                    completion(nil) // error getting field; return nil
                }
            } else {
                if let error = error {
                    print(error)
                }
                completion(nil) // error getting document; return nil
            }
        }
    }
}

And thank you for your service! Hopefully you got to fly a B1-B.

trndjc
  • 11,654
  • 3
  • 38
  • 51
  • bsod! Thanks for the help, I flew UH-1s! I've updated my code to include what you have provided. Unfortunately I'm getting the "error getting field" in the debug. One thing I noticed that doesnt look right to me is the name variable under self.firstNameLabel.text = name doesn't show as a different color (blue) like other variables. – Matt Mixon Aug 16 '20 at 01:25
  • It's not the same color as the other variables because `name` is not an instance property, it's a local property, local to the scope of that completion handler. Are you certain the document exists in the collection with that document ID and that the spelling of the field matches? I notice that in my code where I wrote `if let name = doc.get("firstName") as? String` I spelled it out as `firstName` (with camel casing) while you did not (I think you had it all lowercase). Does that fix it? – trndjc Aug 16 '20 at 02:38
  • I've updated the code to what I currently have in my project. So, the "let uid = dL27eCBT70C4hURGqV7P" does make the debugger return the rest of the information that is contained in that document. The label also displayed "put the firstname data here"...I'm wondering if the issue lies in the fact that the uid pulled from Auth is not the "document" name, but a collection held within each users document. As far as the camel casing, should I leave it all lowercase or use the camel casing? The reason I departed from camel casing is that the "firstname" field inside of the doc is all lowercase – Matt Mixon Aug 16 '20 at 02:53
  • If it's lowercase in the database, it must be lowercase in your code here; the field names must be identically typed. All you need to do to check is log into your Firebase console (on your browser) and hop into the collection and see what the document's ID is. And if you're curious what the ID is of the logged-in user, just print that to console as well. If I had to guess, I would suspect that you let Firestore auto-gen ID both the user and the document, in which case they would be different. But you can figure this out simply by looking through your Firebase console. – trndjc Aug 16 '20 at 02:57
  • That's exactly right. The uid and document id were autogenerated – Matt Mixon Aug 16 '20 at 03:02
  • You need to give the user document a name (the userID) when you create the user document, don’t auto generarte it. Or you could assign a userID property to the document and query for the document using that property. The choice is yours. – trndjc Aug 16 '20 at 03:11
  • bsod, you came through! I'm going to check this answer as accepted...I've been making some progress finally! Any chance you could upvote my question? I'm new here so I don't any reputation at all, but I could definitely use some! – Matt Mixon Aug 16 '20 at 03:50
1

I suspect from the evidence in your question that you are getting a doc, but have an incorrect field name or an uninitialized field in the retrieved doc. As a debug step, replace your getName function with this one, which prints all of the data found in the doc.

func getName(completion: @escaping (_ name: String?) -> Void) {
    let uid = Auth.auth().currentUser!.uid
    let docRef = Firestore.firestore().collection("users").document(uid)

    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")
        }
        completion("put the first name data here after we figure out what's in the doc")
    }
}

Once we know what's in the doc, it should be easy to work out what value to pass to the completion function.

danh
  • 62,181
  • 10
  • 95
  • 136
  • Brilliant...I see what you did here with this code, I'm not tracking 100%, but I'm beginning to understand. SO. This resulted in "document does not exist" and "great success" being printed in the debuger. – Matt Mixon Aug 16 '20 at 02:04
  • This also resulted in the label being updated to "put the first name data here..." – Matt Mixon Aug 16 '20 at 02:06
  • This tells us a lot. It means there's no readable document in the users collection matching the UID of the current user in your swift app. Can you look at the firebase data console in the web? Instead of using auth().currentUser.uid, how about hard-coding a doc ID you see on the console for the users collection. `let uid = "UID of one of the user docs in your users collection found in the browser"`. Also remember that your rules must allow reads on that collection. Have you setup rules yet? – danh Aug 16 '20 at 02:17
  • hmm...I'll give that a shot and report back. I'm wondering if there is in issue with the document name. What I'm trying to do through this code is to match the current logged-in user's uid, to the uid field of a document...and then display the "firstname" field. So, the document name does not equal the uid--you probably already knew that, but I wanted to make sure – Matt Mixon Aug 16 '20 at 02:27
  • Yes, I think I understand what you're after. Now that we see the doc doesn't exist, it likely means that there's no doc in the users collection whose id is the uid of the user logged in on swift – danh Aug 16 '20 at 04:05
  • Hey danh, I was able to figure it out. The solution to my problem was to give the document name (at user creation) a name equal to the uid. If you give my question an upvote, I'll finally have enough reputation to give your help an up vote as well! – Matt Mixon Aug 16 '20 at 20:32
  • Glad you figured it out – danh Aug 16 '20 at 22:02