Firestore/Swift 4: Check username exists? - ios

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. :)

Related

How can I validate textField input with a Firestore query call?

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
}

Wait before calling a function in Swift

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)
}

Check if user exist with firebase 3.0 + swift

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.

Firebase - Unable to execute call to backend

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.

Conditional function return nothing, correct syntax ? iOS

Instead to write a big "if" statement, I want to know if this syntax is ok ?
I just use the word return if the condition is not respected.
#IBAction func SignUpAction(sender: AnyObject) {
if passwordField1.text != passwordField2.text {
// password dont match
// Pop error
return
}
let user = PFUser()
user.username = emailField.text
user.password = passwordField1.text
user.signUpInBackgroundWithBlock { (suceeded: Bool, error: NSError?) -> Void in
if error == nil {
// Log the user
}else{
// Display an error
}
}
}
Or I need to write it like this :
#IBAction func SignUpAction(sender: AnyObject) {
if passwordField1.text == passwordField2.text {
let user = PFUser()
user.username = emailField.text
user.password = passwordField1.text
user.signUpInBackgroundWithBlock { (suceeded: Bool, error: NSError?) -> Void in
if error == nil {
// Log the user
}else{
// Display an error
}
}
}else{
// Error : password dont match
// Pop error
}
}
the first syntax is more clear if need to try many conditions but I'm not sure it's good to write the code like that. Thanks
SOLUTION : (with matt answer and Apple Doc on "guard" :
The first statement need to be write like this :
guard passwordField1.text == passwordField2.text else {
print("pop error")
return
}
Actually, I'd use the first syntax, expressed as a guard statement:
guard passwordField1.text == passwordField2.text else { return }
This is exactly what guard is for: if conditions are not met, go no further. A succession of multiple guard statements (because multiple conditions must be met) is quite standard.

Resources