How can I verify if a username on Firebase is available? - swift

In my Swift app, when signing up a user, the user is prompted to select a username. This username will then be stored in the Firebase realtime database like this: self.ref.child("users").child(user!.uid).setValue(["username": usernameResponse]), where username response is the value entered by the user. This happens as part of the sign up method:
FIRAuth.auth()?.createUserWithEmail(email, password: passwordUltimate) { (user, error) in
// ... if error != nil {
I would like to verify if the username is available before setting its value. Is there some kind of query I could use to check that there are no duplicates?
Here is my database with some sample data (qwerty12345 is the uid):

#IBAction func enterUsername(){
let enteredUsername = usernameText!.text!
let namesRef = ref.childByAppendingPath("/usernames/\(enteredUsername)")
namesRef.observeSingleEventType(.Value, withBlock: {
snap in
if (snap.value is NSNull){
let userNameAndUUID = ["Username" : enteredUsername, "UUID" : self.appDelegate.UUID]
namesRef.setValue(userNameAndUUID)
print("first block")
}else {
print("second block")
//do some other stuff
}
})
}
Alternately:
let checkWaitingRef = Firebase(url:"https://test.firebaseio.com/users")
checkWaitingRef.queryOrderedByChild("username").queryEqualToValue("\(username!)")
.observeEventType(.Value, withBlock: { snapshot in
if ( snapshot.value is NSNull ) {
print("not found)")
} else {
print(snapshot.value)
}
}

Related

Swift retrieve user info from firebase firebase firestore

I want to retrieve the current logged in user Information (name and email) that was stored in the firestore in the registration function, the email and name should be displayed in textfield.
I can retrieve the email successfully because I’m using the Auth.auth().currentUser and not interacting with the firesotre while the name is not working for me.
what I’m suspecting is that the path I’m using for reaching the name field in firesotre is incorrect.
var id = ""
var email = ""
override func viewDidLoad() {
super.viewDidLoad()
userLoggedIn()
self.txtEmail.text = email
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getName { (name) in
if let name = name {
self.txtUserName.text = name
print("great success")
}
}
}
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
}
print (uid)
Firestore.firestore().collection("users").document(uid).getDocument { (docSnapshot, error) in
if let doc = docSnapshot {
if let name = doc.get("name") 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
}
}
}
func userLoggedIn() {
if Auth.auth().currentUser != nil {
id = Auth.auth().currentUser!.uid
//email = Auth.auth().currentUser!.email
} else {
print("user is not logged in")
//User Not logged in
}
if Auth.auth().currentUser != nil {
email = Auth.auth().currentUser!.email!
} else {
print("user is not logged in")
//User Not logged in
}
}
When I run this code the email is displayed and for the name "error getting field" gets printed so what I think is that the name of the document for user is not the same as the uid therefore the path I’m using is incorrect, the document name must be autogenerated.
So is the solution for me to change the code of the registration function?
can the user document be given a name (the userID) when I create the user document, instead of it being auto generarte it, if that’s even the case.
Here is the registration code for adding documents to firestore:
let database = Firestore.firestore()
database.collection("users").addDocument(data: [ "name" :name, "email" : email ]) { (error) in
if error != nil {
//
}
an here is a snapshot of my firestore users collection
When creating a user;
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
  // ...
}
At first you can only save email and password. (For now, that's how I know.)
But after you create the user, you can update the user's name.
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges { error in
  // ...
}
Use userUID when saving user information in Firestore.
If you drop the document into firebase, it will create it automatically. But if you save the user uid, it will be easy to access and edit.
func userSave() {
let userUID = Auth.auth().currentUser?.uid
let data = ["name": "ABCD", "email": "abcd#abcd.com"]
Firestore.firestore().collection("users").document(userUID!).setData(data) { error in
if error != nil {
// ERROR
}
else {
// SUCCESSFUL
}
}
}
If you are saving user information in Firestore, you can retrieve information very easily.
func fetchUser() {
let userUID = Auth.auth().currentUser?.uid
Firestore.firestore().collection("users").document(userUID!).getDocument { snapshot, error in
if error != nil {
// ERROR
}
else {
let userName = snapshot?.get("name")
}
}
}
For more detailed and precise information: Cloud Firestore Documentation
If you see missing or incorrect information, please warn. I will fix it.
There's a distinction between a Firebase User property displayName and then other data you're stored in the Firestore database.
I think from your question you're storing other user data (a name in this case) in the Firestore database. The problem is where you're storing it is not the same as where you're reading it from.
According to your code here's where it's stored
database.collection("users").addDocument(data: [ "name" :name,
which looks like this
firestore
users
a 'randomly' generated documentID <- not the users uid
name: ABCD
email: abcd#email.com
and that's because addDocument creates a documentID for you
Where you're trying to read it from is the actual users UID, not the auto-created documentID from above
Firestore.firestore().collection("users").document(userUID!)
which looks like this
firestore
users
the_actual_users_uid <- the users uid
name: ABCD
email: abcd#email.com
The fix it easy, store the data using the users uid to start with
database.collection("users").document(uid).setData(["name" :name,

Swift closures and error handling

hello, i need some help with function and or possibly closures in that function. I want my function to check the firestore users collection for duplicate documents (usernames). If a duplicate is found i want to display a message, if a duplicate is not found, create a new user. i have folowing code:
func checkIfUserExists(username: String, completion: #escaping (Bool) -> Void) {
let docRef = db.collection("users").document(username)
docRef.getDocument { (document, error) in
if error != nil {
print(error)
} else {
if let document = document {
if document.exists {
completion(true)
} else {
completion(false)
}
}
}
}
}
and i call the function with:
if let username = textField.text, username.count > 8 { // Username needs to be more then 8 Char
checkIfUserExists(username: username) { (doesExist) in
if doesExist {
print("user exists")
} else {
print("new User can be created")
}
}
} else {
print("Username needs to be more then 8 char")
}
}
It works, but i have the feeling it is not good practice and i'm making detours. Is this the right way to do it ?
I think the way you're doing it now should work well, but another option to prevent you from having to do a read of the database before writing is to use security rules. For example, if this is the structure of your users collection...
users: [
username1: { // doc ID is the username
userid: abcFirebaseUserId, // a field for the uid of the owner of the username
//...etc
}
]
...then you can use the following rules:
match /users/{username} {
allow create: if request.auth.uid != null;
allow update, delete: if resource.data.userId = request.auth.uid;
}
This allows any authenticated user to create a new username, but only the owner of that username can update it or delete it. If you aren't allowing users to change their username, you wouldn't even have to worry about the second rule. Then, in the client, you go right to creating a username, like so:
func createUsername(username: String, completion: #escaping (String?) -> Void) {
guard let userId = Auth.auth().currentUser.uid else {
completion("no current user")
return
}
let docRef = db.collection("users").document(username)
docRef.setData(data:[userId: userId]) { error in
if let error = error {
completion(error.debugDescription)
} else {
completion(nil)
}
}
}
This would write the new username to the database and pass an error to the closure if there is one. If the username already exists, an insufficient permissions error would be present. When checking if the user exists, you could display the error or alert the user however you wanted.
createUsername(username: username) { err in
if let err = err {
print("user exists")
} else {
print("new User has been created")
}
}
Just a suggestion though. I think they way you're doing it now is fine, too!

UID might be blank?

I'm using Firebase for keeping track of user's subscription information. They have to register in Firebase and then, after their purchase completed, Firebase will setup/renew their subscription. The database is structured
root
-> users
-> UID
->user info
I add new user info like this:
func setExpiration() {
guard let user = Auth.auth().currentUser else {
return
}
let usersRef = Database.database().reference().child("Users")
usersRef.observeSingleEvent(of: .value, with: { (dataSnapshot) in
if let dict = dataSnapshot.value as? [String : [String : String]] {
...
// Checks to see if the user exists in the database yet.
if let userDict = dict[user.uid] {
...
usersRef.child(user.uid).updateChildValues(keyedValues, withCompletionBlock: { (error, databaseReference) in
if let error = error {
...
return
}
...
})
// User does not exist in database and needs added.
} else {
...
usersRef.updateChildValues([user.uid : keyedValues], withCompletionBlock: { (error, databaseReference) in
if let error = error {
...
return
}
...
})
}
}
})
}
My problem is that sometimes lately, the user info goes straight in the users bucket instead of in their UID. After playing around with this I cannot replicate the issue. So far it seems not all of our users have this problem. Is it possible that the uid might be coming back blank at times and that's causing it to write to the wrong place?

Firebase how to create users?

I am not familiar with the new Firebase. How do I create new users? The code below I Signup and auth new user. If I need to create this new user under "Customers" in Firebase Database, what code do I need to add? Thanks!
FIRAuth.auth()?.createUserWithEmail(email, password: password, completion: { (user, err) in
if err != nil {
self.showAlert("Can't Register", msg: "Please enter email and password")
} else {
NSUserDefaults.standardUserDefaults().setValue(user?.uid, forKey: "uid")
FIRAuth.auth()?.signInWithEmail(email, password: password, completion: { (user, error) in
})
self.performSegueWithIdentifier("toSecondVC", sender: self)
}
})
FIRAuth.auth()?.createUserWithEmail(email, password: password, completion: { (user, err) in
if err != nil {
self.showAlert("Can't Register", msg: "Please enter email and password")
} else {
NSUserDefaults.standardUserDefaults().setValue(user?.uid, forKey: "uid")
FIRDatabase.database().reference().child("Customers").setValue([user!.uid : "true"])
//Or if you want to save the users Details too:-
/*
FIRDatabase.database().reference().child("Customers").setValue([user!.uid : ["email" : email,"password" : password]])
*/
self.performSegueWithIdentifier("toSecondVC", sender: self)
}
})
Also might i suggest reading this : Firebase iOS - Save Data
The answer above is correct, but I'd recommend doing it this way:
var values = [Any : Any] // This is a dictionary where you can store user details
values["userUID"] = // user's uid
values["usersName"] = // user's name
// etc.
let customerRef = databaseReference.child("Customers") // You could also add a child with the user's UID in order to identify each user
customerRef.updateChildValues(values, withCompletionBlock: { (error, ref) in
if error != nil {
// display error.code and error.localizedDescription to user if needed
} else if ref != [] {
// Success!
// If needed, save the user to NSUserDefaults and perfrom a segue to another ViewController
} else {
// There was an error, but not sure what happened. Let the user know how they can fix the problem (maybe add the details to the database later)
}
})

Firebase does not create a Facebook account User UID using the Facebook App User ID

I am developing an iOS app in swift that requires a user to create an account by connecting their Facebook account. A month ago, when I was testing by signing up with my own Facebook account, the User UID created by Firebase was in the format "facebook:(facebook app id)". This was what I wanted.
However, lately whenever a user creates a new acconut by connecting their Facebook account with my app, Firebase creates a User UID using a random string. For example: "E9FaL87wRmOKfhen2S6yszhCwtx1". Could this be because of the new Firebase update? Should I go through the migration process?
Here is my code for account creation:
#IBAction func facebookAuthAction(sender: AnyObject) {
let facebookLogin = FBSDKLoginManager()
facebookLogin.logInWithReadPermissions(nil, fromViewController: nil, handler: {(facebookResult, facebookError) -> Void in
if facebookError != nil {
print("Facebook login failed. Error \(facebookError)")
} else if facebookResult.isCancelled {
print("Facebook login was cancelled.")
} else {
let accessToken = FBSDKAccessToken.currentAccessToken().tokenString
FIREBASE_REF.authWithOAuthProvider("facebook", token: accessToken, withCompletionBlock: { error, authData in
if error != nil {
print("Login failed. \(error)")
} else {
print("Logged in! \(authData)")
NSUserDefaults.standardUserDefaults().setValue(authData.uid, forKey: "uid")
FIREBASE_REF.childByAppendingPath("users").observeEventType(.Value, withBlock: {snapshot in
if snapshot.hasChild(authData.uid) == false {
self.createNewFBUser(authData.uid)
}
})
self.performSegueWithIdentifier("loginSegue", sender: self)
}
})
}
})
}
func createNewFBUser(uid: String) {
var emailAddress:String = ""
var firstName:String = ""
var lastName:String = ""
var pictureURL:String = ""
let parameters = ["fields": "email, first_name, last_name, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parameters).startWithCompletionHandler {(connection, result, error) -> Void in
if error != nil {
print(error)
return
}
if let email = result["email"] as? String {
emailAddress = email
}
if let first_name = result["first_name"] as? String {
firstName = first_name
}
if let last_name = result["last_name"] as? String {
lastName = last_name
}
if let picture = result["picture"] as? NSDictionary, data = picture["data"] as? NSDictionary, url = data["url"] as? String {
pictureURL = url
}
let newUser = [
"provider": "facebook",
"firstName": firstName,
"lastName": lastName,
"email": emailAddress,
"picture": pictureURL
]
FIREBASE_REF.childByAppendingPath("users").childByAppendingPath(uid).setValue(newUser)
}
}
When you upgrade your project to the new Firebase Console, your users are migrated to the new authentication back-end.
Newly created users after the upgrade will get a uid in the new format. See this post on the firebase-talk group for more information about the change and when it will also be applied to existing (non-upgraded) Firebase apps.
Note that Firebase has recommended against depending on the format of the uid for years. It is best to treat it as an opaque string that identifies the user.