简体   繁体   中英

Displaying firebase child from user.uid to username in Swift 3

I have a view controller for My Profile. Logging in allows the profile page to appear without errors but when signing up, app crashes when pressing the contacts button located at bottom of view controller as seen below.

The process:

User Signs Up:

func signUp(_ email: String, usersname: String, password: String, data: Data!, loginHandler: LoginHandler?) {

    FIRAuth.auth()?.createUser(withEmail: email, password: password,  completion: { (user, error) in

        if error != nil {
            // Show error to user
            self.handleFirebaseErrors(err: error as! NSError, loginHandler: loginHandler)

        } else { // success creating user

            if user?.uid != nil { // if there is a valid user id 

                // Store user to database
                self.setUserInfo(user, usersname: usersname, email: email, password: password, data: data!)

                // Log In the user
                self.login(email: email, password: password, loginHandler: loginHandler)
            }
        }
    })

}

As in the signUp(), setUserInfo() is called, which contains images, and then calls saveUser()

Save User

func saveUser(_ user: FIRUser!, usersname: String, email: String, password: String) {

    // Create the user dictionary info
    let userInfo = ["email": user.email!, "password": password, "usersname": usersname, "uid": user.uid, "photoUrl": String(describing: user.photoURL!)]

    // create user reference
    let userRef = DataService.Instance.dbRef.child("riders").child(user.uid)

    // Save the user info in the database
    userRef.setValue(userInfo)

}

Logs In

func login(email: String, password: String, loginHandler: LoginHandler?) {

    FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in

        if error != nil {
            self.handleFirebaseErrors(err: error as! NSError, loginHandler: loginHandler)
        } else {
            loginHandler?(nil, nil)
        }
    })

}

The problem here is in saveUser(): At the moment, firebase shows the user.uid but I want it to show the username of the user.

let userRef = DataService.Instance.dbRef.child("riders").child(usersname) 

With the above code, once the contacts button is pressed on the RidersVC, it crashes the app with error:

fatal error: unexpectedly found nil while unwrapping an Optional value

on line 56 of MyProfileVC:

let imageUrl = String(user.photoUrl)

Any ideas as how I can get the username to be displayed as the child of "riders" instead of the user.uid without it crashing?

MyProfileVC.swift

if FIRAuth.auth()?.currentUser == nil {

        let vc = UIStoryboard(name: "Rider", bundle: nil).instantiateViewController(withIdentifier: "Login")
        present(vc, animated: true, completion: nil)

    } else {

        dbRef.child("riders/\(FIRAuth.auth()!.currentUser!.uid)").observe(.value, with: { (snapshot) in
            DispatchQueue.main.async(execute: {


                let user = User(snapshot: snapshot)
                self.username.text = user.usersname
                self.email.text = FIRAuth.auth()?.currentUser?.email

                let imageUrl = String(user.photoUrl)

Firebase Database Structure: (how I want it to be)

{
  "riders" : {
    "rider 1" : {
      "email" : "rider1@me.com",
      "password" : "whatever",
      "photoUrl" : "https://firebasestorage.googleapis.com/...",
      "usersname" : "rider 1"
    }
  }
}

User.swift

struct User {

let usersname: String!
let email: String!
let password: String!
let photoUrl: String!
var ref: FIRDatabaseReference?
var key: String


init(snapshot: FIRDataSnapshot) {
    key = snapshot.key
    ref = snapshot.ref

    let snapshotValueUsersname = snapshot.value as? NSDictionary
    usersname = snapshotValueUsersname?["usersname"] as? String

    let snapshotValueEmail = snapshot.value as? NSDictionary
    email = snapshotValueEmail?["email"] as? String

    let snapshotValuePass = snapshot.value as? NSDictionary
    password = snapshotValuePass?["password"] as? String

    let snapshotValuePhoto = snapshot.value as? NSDictionary
    photoUrl = snapshotValuePhoto?["photoUrl"] as? String

}

Firebase structure - (the way it is now)

{
  "drivers" : {
    "RideRequests" : {
      "europeanjunkie" : {
        "active" : true,
        "latitude" : "45.267",
        "longitude" : "-66.059",
        "userId" : "5c17ByRJljZFcM703Vqn5eSFwYJ3",
        "username" : "europeanjunkie"
      }
    }
  },

   "riders" : {
    "5c17ByRJljZFcM703Vqn5eSFwYJ3" : {
      "email" : "europeanjunkie@me.com",
      "password" : "whatever",
      "photoUrl" : "https://firebasestorage.googleapis.com",
      "uid" : "5c17ByRJljZFcM703Vqn5eSFwYJ3",
      "usersname" : "europeanjunkie"
     }
  }
}

Here's some stuff to consider - a little, some or all may get you headed in the right direction. Also, you can probably remove all of the DispatchQueue calls as Firebase does most of the heavy lifting for you, and with proper code structure, they are not needed.

1) A Swifty user class

class UserClass {
    var usersname = ""
    var email = ""
    var password = ""
    var photoUrl = ""
    var uid = ""

    init(withSnapshot: FIRDataSnapshot) {
        let dict = withSnapshot.value as! [String:AnyObject]

        uid = withSnapshot.key
        usersname = dict["usersname"] as! String
        email = dict["email"] as! String
        password = dict["password"] as! String
        photoUrl = dict["photoUrl"] as! String
    }
}

note that we are using the var uid of each user to identify them (their 'key')

The structure that matches that class

users
  uid_0
    email: "bill@email.com"
    password: "myPassword"
    photoUrl: "http://www.url.com"
    usersname: "Bill"
  uid_1
    email: "leroy@email.com"
    password: "aPassword"
    photoUrl: "http://www.anotherUrl.com"
    usersname: "Leroy"

Notice again the users and their associated info are stored within the /users node in each child node that has that users uid as the key.

And some code that reads in uid_0, prints the uid and name. This code is a one-shot so it reads uid_0, but does NOT leave an observer attached to the node.

let userRef = rootRef.child("users/uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in        
     let aUser = UserClass(withSnapshot: snapshot)
     print("uid \(aUser.uid)  has name  \(aUser.usersname)")
})

Now the Geofire node would like something like this

user_locations
  uid_0
    //geofire data
  uid_1
    //geofire data

So now there is a direct correlation between the users node and their location.

In general, it's a good idea to disassociate node names (keys, which are static data) from the data they contain, which is dynamic.

With the structure in the initial question, imagine if 'europeanjunkie' changed his name to 'europeanjunkieDude'. Every place you reference 'europeanjunkie' would then have to be changed - and if it's used as a key, the entire node would have to be read in, deleted, updated, and re-written.

Using child keys created by Firebase, uid's and childByAutoId(), removes that issue.

Hope that helps!

In my opinion, if you want to query the username as the keyword. There are two possible ways to struct your dictionary.

First, use childByAutoId , username and userid will be at the same level, so you can get which value you like.

{
  "riders" : {
    "-KQaU9lVcUYzIo52LgmN" : {
      "email" : "rider1@me.com",
      "password" : "whatever",
      "photoUrl" : "https://firebasestorage.googleapis.com/...",
      "usersname" : "rider 1",
      "userid" : "rider 1"
    }
  }
}

Second, make username as the child of riders. However, there would be tons of Mike.

{
  "riders" : {
    "username" : {
      "email" : "rider1@me.com",
      "password" : "whatever",
      "photoUrl" : "https://firebasestorage.googleapis.com/...",
      "userid" : "rider 1"
    }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM