I am working on a function that handles user registration and in the process, check if the selected username entered by the user is taken or not to inform the user to select a different one. I have the below code to accomplish this scenario:
#IBAction func proceedPressed(sender: AnyObject) {
/**********************Perform Validation************************/
if(self.emailTxtField.text != "" && self.passwordTxtField.text != "")
{
print("Email and Password not empty")
self.usernameValidation({(result) -> Void in
if(result == false)
{
print("Result False")
self.usernameErrorLabel.text = "Username Taken"
}else{
print("Result True")
//Username is available...Proceed
self.usernameErrorLabel.text = ""
FIRAuth.auth()?.createUserWithEmail(self.emailTxtField.text!, password: self.passwordTxtField.text!) { (user, error) in
if(error == nil)
{
print("Creating User with Email")
/*Create the user object as submitted*/
self.dbReference.child("users").child(user!.uid).setValue(["username": self.emailTxtField.text!,"name":self.nameTxtField.text!, "email":self.emailTxtField.text!, "mobile":self.mobileTxtField.text!, "homeAddress":"N", "workAddress":"N", "otherAddress":"N", "profilePictureRef":"N","telephone":"0","friendsCount":0, "retailersCount":0])
}else{
print("Error occured: \(error?.description)")
}
}//end of createUserWithEmail
}
})
}else{
print("Error: Email or Password field is empty")
}
}
and to check the username:
func usernameValidation(completion: (result: Bool) -> Void)
{
print("Username is: \(self.usernameTxtField.text!)")
dbReference.child("usernamesTaken").queryOrderedByValue().queryEqualToValue(self.usernameTxtField.text!).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot!) -> Void in
print(snapshot.childrenCount)
if(snapshot.childrenCount == 0)
{
print("result is true in username validation")
//Username Available
completion(result:true)
}else{
print("result is false in username validation")
//Username Taken
completion(result:false)
}
})
}
The problem with the above is that the full code doesn't seem to execute. When button pressed, I get the following messages in console:
- Email and Password not empty
- Username is: [value entered in usernameTxtField.text
and then nothing more. Although I wrote many print statements to try and see where this is stopping, but this is the furthest the code went in terms of printing the statements.
Is there something wrong here that I am missing out?
Thanks in advance.
I did some more testing and then discovered the issue through the xcode console. I copied the following from the firebase website to test fetching the data:
ref.child("users").child(userID!).observeSingleEventOfType(.Value, withBlock: { (snapshot) in
// Get user value
let username = snapshot.value!["username"] as! String
let user = User.init(username: username)
// ...
}) { (error) in
print(error.localizedDescription)
}
The above showed an error that is "Permission Denied". Following that I edited the Rules in the database section in the console and allowed .read and .write and that did it. I thought I would post the details just in case someone else gets stuck.
Related
So my goal is to validate a textfield by checking if that value is within any of the documents in the Firestore collection. So in my other validation function, I can return a String and show an alert with the error like so:
func validateFields() -> String? {
if nameTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
emailTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
passwordTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
currentGradeTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""{
showAlert(title: "Missing Fields", message: "Please fill in all fields.")
return "Issue With Fields"
}
let cleanedPassword = passwordTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let properGradeSelected = currentGradeTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let validSchoolID = schoolIDTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if Utilities.isPasswordValid(cleanedPassword) == false {
showAlert(title: "Invalid Password", message: "Please ensure that your password contains 8 characters, contains a special character and a number as well.")
return "Issue With Password"
}
if Utilities.isGradeValid(properGradeSelected) == false {
showAlert(title: "Invalid Grade", message: "Please ensure that your current grade is valid.")
return "Issue With Grade Input"
}
if Utilities.isSchoolIDValid(validSchoolID) == false {
showAlert(title: "Invalid School ID Format", message: "The School ID entered has the incorrect format.")
return "Issue With School ID input."
}
return nil
}
Then I call it when the 'Sign Up' button is pressed like so:
#IBAction func signupPressed(_ sender: UIButton) {
//Validate the fields
let validationError = validateFields()
if validationError != nil {
return
} else {
//Create the user
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in ....
This works perfect. Now since I want to use a Firestore query in a function, I can't return the String like how I did in the other ones without getting errors, so I'm quite confused on how to go about doing this.
This is the function I have so far:
func determineIfIDIsValid() {
let schoolIDText = schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
db.collection("school_users").whereField("school_id", isEqualTo: schoolIDText).getDocuments { (querySnapshot, error) in
guard error == nil else {
return
}
guard let query = querySnapshot?.isEmpty else { return }
if query == true {
//Show error alert
} else {
return
//Continue With Signup flow
}
}
}
I've tried declaring a variable before the query whether it be a String or Bool, changing the value after the query, and using logic to return a String but that didn't work either. How can I use this function to validate the specific field without getting any errors?
func determineIfIDIsValid(_ callback: #escaping (Bool) -> ()) {
let schoolIDText = schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
db.collection("school_users").whereField("school_id", isEqualTo: schoolIDText).getDocuments { (querySnapshot, error) in
guard error == nil else {
return
}
guard let query = querySnapshot?.isEmpty else { return }
if query == true {
//Show error alert
callback(true)
} else {
callback(false)
//Continue With Signup flow
}
}
}
Firebase calls are async. You need use closure
determineIfIDIsValid() { res in
if res {
//Show alert
} else {
//Continue With Signup flow
}
I'm using Firestore for my iOS app project and I would like to add some additional data to the user, so I created a collection called "profiles" where I'd like each document named as the Auth.auth().currentUser.uid so I can retrieve the data later for each user (or is there a better way?).
These documents in Firestore are created when the user register a new account:
func createUser() {
db.collection("profiles").document("\(currentUser?.uid)").setData([
"name": name,
"surname": surname,
"email": email,
"shelter": isMember == true ? shelters[selectedShelter] : shelters[0],
"photoURL": imageURL ?? "",
"reputation": 0,
"uuid": "\(uuid)"
])
{ err in
if let error = err {
print("Error ading document: \(error)")
} else {
print("Document added")
}
}
}
But, at the moment this function is called (when clicking on the "Register" button), the currentUser doesn't exist as it is defined to Auth.auth().currentUser, so the currentUser.uid = nil in my "profiles" documents in Firestore.
The function createUser is called in my Register function:
func register() {
if self.email != "" {
if self.pass == self.repass {
Auth.auth().createUser(withEmail: self.email, password: self.pass) { (res, err) in
if err != nil {
self.error = err!.localizedDescription
self.alert.toggle()
return
}
// Success registering a new user
viewRouter.currentView = "Home"
DispatchQueue.main.async {
self.createUser()
}
}
}
else {
self.error = "Les mots de passe ne correspondent pas"
self.alert.toggle()
}
}
else {
self.error = "Veuillez remplir tous les champs"
self.alert.toggle()
}
}
I thought about waiting that Auth.auth().currentUser exists before calling the createUser function. I tried using DispatchQueue.main.async without success.
Any idea? Or maybe a better way to achieve my goal - which is to add more data to each user (e.g. : reputation, shelter,...)?
Thank you for your help!
What you are doing is correct, and you can actually get that newly-created user using the res parameter in your completion handler.
Example
Auth.auth().createUser(withEmail: self.email, password: self.pass) { (res, err) in
if err != nil {
self.error = err!.localizedDescription
self.alert.toggle()
return
}
// Successfully registered a new user, get the user id
guard let currUserId = res?.user.uid else { return }
// ...
// Maybe add uid as a parameter of createUser
createUser(id: currUserId)
}
I'm trying to understand where i may be going wrong with the following code. I have tried to look up a way to check if the users collection contains a string within one of it's documents within a username field.
Structure looks something like this
#users(collection)
#userID(document)
#name:String
#email:String
#username:String
#userID(document)
#name:String
#email:String
#username:String
What i am trying to check is if within users, and within any document, does the string exist in the username field. Found a possible solution to this question (among others) here by calling a function to query for the username.
I created the function in an extension:
extension UITextField {
func checkUsername(username: String, completion: #escaping(Bool) -> Void) {
let usernameRef = Database.database().reference()
usernameRef.child("users").queryOrdered(byChild: "username").queryEqual(toValue: username).observeSingleEvent(of: DataEventType.value, with: { (snapshot: DataSnapshot) in
if snapshot.exists() {
completion(true)
}
else {
completion(false)
}
})
}
}
and called it on the text field within textFieldDidEndEditing so it can perform the check upon the user attempting to claim a username.
func textFieldDidEndEditing(_ textField: UITextField) {
if activeField == usernameField {
textField.checkUsername(username: usernameField.text!) { isExist in
if isExist {
print("Username exists")
} else {
print("Username does not exist")
}
}
}
}
However, this always returns 'Username does not exist' to the console, even when the username does exist.
There is currently 1 username within the database called test, so attempting to enter test should return 'Username exists' and entering testtwo should return 'Username does not exist'.
What can i be missing here? I am assuming its not querying the database correctly?
Thanks to Frank for pointing out i was calling the Realtime Database rather than Firestore. Updated my code to return a true or false value when checking documents for anything stored within 'username' and thought i would share my updated code if anyone else is attempting to check 'usernames' within their Firestore DB.
Created an extension on the UITextField which is called when i require checking a username is available:
func checkUsername(field: String, completion: #escaping (Bool) -> Void) {
let collectionRef = db.collection("users")
collectionRef.whereField("username", isEqualTo: field).getDocuments { (snapshot, err) in
if let err = err {
print("Error getting document: \(err)")
} else if (snapshot?.isEmpty)! {
completion(false)
} else {
for document in (snapshot?.documents)! {
if document.data()["username"] != nil {
completion(true)
}
}
}
}
}
Then on the UITextField textDidEndEditing, i call the function is the active field is the username field:
func textFieldDidEndEditing(_ textField: UITextField) {
if activeField == usernameField {
if textField.text?.isEmpty == false {
textField.checkUsername(field: textField.text!) { (success) in
if success == true {
print("Username is taken")
// Perform some action
} else {
print("Username is not taken")
// Perform some action
}
}
}
}
}
This then returns 'Username is taken' if i attempt to enter a username that exists in any of the documents username field, or 'Username is not taken' if nothing is found.
If anyone has any feedback to improve the code or any questions, let me know. I'll happily update for reference. :)
I have a app that after the user use Firebase auth it store the data on the Firebase database. Before storing the data, I want to check if the username the user give already exist in the database. So if it not exist I could give the user this unique username(like every user have a unique username). So I have a textField where the user enter his username, and then press Next. Then the app should check if the username exist or not, and tell the user if he need to change it.
So the code I used to check if the username exist:
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.hasChild(self.usernameTextField.text!){
print("user exist")
}else{
print("user doesn't exist")
}
})
So every time the next button is pressed, this code is called. The problem with this is that the result always remain the same as the first search (even after the textField value change).
For example, if I search Jose, and Jose exist in my database so is going to print "user exist". But when I change the textField to name that don't exist, it still show "user exist".
I figured out I need to change the .Value to FIRDataEventType.Value
if (usernameTextField.text?.isEmpty == false){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observeSingleEventOfType(FIRDataEventType.Value, withBlock: { (snapshot) in
if snapshot.hasChild(self.usernameTextField.text!){
print("true rooms exist")
}else{
print("false room doesn't exist")
}
})
struct ModelUser {
var id: String
var name: String
init(data: DataSnapshot) {
// do init stuff
}
}
func isUserRegistered(with id: String, completion: #escaping (_ exists: Bool, _ user: ModelUser?) -> ()) {
DatabaseReference.users.child(id).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
// user is already in our database
completion(true, ModelUser(data: snapshot))
} else {
// not in database
completion(false, nil)
}
}
}
This worked for me in a similar situation as yours. You can also go the Rx way like this.
enum CustomError: Error {
case userNotRegistered
var localizedDescription: String {
switch self {
case .userNotRegistered:
return "Dude is not registered..."
}
}
}
func isUserRegistered(with id: String) -> Observable<(ModelUser)> {
let reference = DatabaseReference.users.child(id)
return Observable<ModelUser>.create({ observer -> Disposable in
let listener = reference.observe(.value, with: { snapshot in
if snapshot.exists() {
observer.onNext(ModelUser(data: snapshot))
observer.onCompleted()
} else {
observer.onError(CustomError.userNotRegistered)
}
})
return Disposables.create {
reference.removeObserver(withHandle: listener)
}
})
}
The key in both cases is using the .exists() method of the snapshot.
I am trying to create a function that takes a username as a parameter and checks to see if that username is taken (by comparing it to other PFUsers in the Parse database. This function is in my view controller class. (I know there are similar questions to this but they do not provide quality answers and are more general than this or are not in Swift).
func usernameIsTaken(username: String) -> Bool {
//bool to see if username is taken
var isTaken: Bool = false
//access PFUsers
var query : PFQuery = PFUser.query()!
query.whereKey("User", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
return isTaken
}
The problem is that the condition in the if statement is always false so "Username is available" always prints in the console even if the username is taken."Username is taken" is never printed even when the username is taken. What should I put in the nested if statement to check if the username matches another PFUser?
You are querying for User (class) key, but you need to query for a specific key, for example email.
// First get user's inputted email
let enteredEmailAddress = "sample#gmail.com"
// Then query and compare
var query = PFQuery(className: "_User")
query.whereKey("email", equalTo: enteredEmailAddress)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
Just thought I would throw this out there, since this doesn't seem to be well known by people as I've answered a similar question before. Parse does this kind of checking for the user class automatically. If you're trying to create a new user with any of the default fields duplicated in Parse i.e username, email, etc, then Parse will not allow user signup. This is done automatically, with you having to do nothing, except for present the error so the user knows why they weren't able to sign up successfully. An example of signing a user up that checks for username email etc duplicates follows below:
user.signUpInBackgroundWithBlock {
(succeeded: Bool, signupError: NSError?)
-> Void in
if signupError == nil {
//present new controller
println("Signed up")
}
else {
if let errorString = signupError!.userInfo?["error"] as? NSString
{
error = errorString as String
}
else {
error = "We're sorry, an error ocured! Please try again."
}
self.displayAlert("Could not sign up", error: error)
}
}
}
Check the error code. Last time I did this, code 202 = Username Taken, code 203 = e-mail taken.
if signupError == nil {
print("User \(user.username!) signed up OK!")
} else if signupError?.code == 202 {
print("Username taken. Please select another")
} else if signupError?.code == 203 {
print("e-Mail taken. Please select another")
}