Swift closures and error handling - swift

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!

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,

Code not executing in docRef.getDocument?

I have a function to check whether an account already exists in the firestore database so that the user can register for one. I have an bool exists to return whether the account exists or not but the code in getDocument() is not executing and assigning the values properly.
func checkAccountExists(username: String) -> Bool {
let docRef = accountsRef!.document(username)
var exists: Bool!
docRef.getDocument { (document, error) in
if let document = document, document.exists {
exists = true
} else {
exists = false
}
}
return exists //Unexpectedly found nil error
}
Not only does Doug correctly point out that the function is async, in Swift, you should not attempt a function (or a computed property) that returns an async value. Instead, consider a function with an async completion that passes the value as an argument.
func checkAccountExists(username: String, completion: #escaping (_ exists: Bool) -> Void) {
accountsRef!.document(username).getDocument { (document, error) in
if let document = document,
document.exists {
completion(true)
} else {
if let error = error {
print(error)
}
completion(false)
}
}
}
Usage
checkAccountExists(username: someUsername) { (exists) in
if exists {
print("user exists")
} else {
print("user doesn't exist or there was an error")
}
}
However, since the database could return an error (even if the user exists), consider returning a Result instead of a Bool. But if you just want a pass/fail mechanism that doesn't decipher between errors and a user truly not existing, this will work.

Problem with deleting account and documents with Firestore

I want to delete the user account and all of its documents in Firestore, but with asynchronous queries, Firebase deletes the account before all documents.
Because of that I get an auth error because when firestorm delete the lasts documents, the user no longer exist.
db.collection("users").document(self.user.uid).collection("sachets").getDocuments() { (QuerySnapshot, err) in
if let err = err{
print("Erreur de lecture : \(err)")
} else {
for document in QuerySnapshot!.documents {
db.collection("users").document(self.user.uid).collection("sachets").document(document.documentID).delete(){ err in
if let err = err {
print(" 🔴 Probleme de suppression des docuemnts \(err)")
} else {
print(" 🔵 Documents supprimés")
}
}
}
}
}
self.user?.delete { error in
if let error = error {
print(" 🔴 Probleme de suppression du compte Utilisateur \(error)")
} else {
print(" 🔵 Utilisateur supprimé")
}
}
Someone can tell me how to do ?
thanks
Alright, I set up a test-project and solve this. You also need to make sure to remove all the data from the user fields. For instance, if you're having name, age etc... at the user node.
First I created a function to fetch the current users uid:
func currentUser() -> String {
return Auth.auth().currentUser!.uid
}
Then I created a function to remove all the data for the logged in user. This will first remove all the subcollection data and then it will remove the document for the userID.
To handle asynchronous methods you can use DispatchGroup to notify when all the asynchronous calls are done.
One side note: Make sure to NEVER force-unwrap a value if you can't 100% guarantee there are documents. Otherwise, your app will likely crash. To solve this either use guard or if let to solve this problem.
// Where "user" is the result from currentUser()
func removeData(from user: String) {
db.collection("users").document(user).collection("sachets").getDocuments { (snapshot, error) in
if let error = error {
// Handle error
} else if let documents = snapshot?.documents {
// Using if let to see if there are documents
// Time to delete all the subCollection for the user
self.deleteSubCollectionData(for: user, documents, completion: {
// Once that done, make sure to delete all the fields on the highest level.
self.db.collection("users").document(user).delete(completion: { (error) in
if let error = error {
// Handle error
} else {
// Delete the account
self.deleteAccount()
}
})
})
}
}
}
// This function will remove the subCollectionData
fileprivate func deleteSubCollectionData(for user: String, _ documents: [QueryDocumentSnapshot], completion: #escaping () -> ()) {
let group = DispatchGroup()
documents.forEach({
group.enter()
self.db.collection("users").document(user).collection("sachets").document($0.documentID).delete(completion: { (error) in
if let error = error {
// Handle error
}
group.leave()
})
})
// Once the dispatchGroup is done...
group.notify(queue: .main) {
completion()
}
}
And in the end...
func deleteAccount() {
Auth.auth().currentUser?.delete { (error) in
if let error = error {
print(error)
} else {
print("Deleted account")
}
}
}
If you don't remove all the levels of data, then there still will be data in Firestore.
Your getDocuments() method is asynchronous, so you should delete the account only after having deleted the documents.
Just put the user?.delete method inside the getDocuments() callback
db.collection("users").document(self.user.uid).collection("sachets").getDocuments() { (QuerySnapshot, err) in
if let err = err{
print("Erreur de lecture : \(err)")
} else {
for document in QuerySnapshot!.documents {
db.collection("users").document(self.user.uid).collection("sachets").document(document.documentID).delete(){ err in
if let err = err {
print(" 🔴 Problème de suppression des documents \(err)")
} else {
print(" 🔵 Documents supprimés")
}
}
}
}
self.user?.delete { error in
if let error = error {
print(" 🔴 Problème de suppression du compte Utilisateur \(error)")
} else {
print(" 🔵 Utilisateur supprimé")
}
}
}

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

CloudKit, retrieve user information, such as first name on sign up

During SignUp on my app, I want to retrieve information, such as first name, from iCloud,I then want to store this in my own cloud kit database. How do I access user information from iCloud, without having to ask the user themselves for these relevant fields?
I was able to get it working with this in XCode 8 iOS 10 beta 2:
CKContainer.default().requestApplicationPermission(.userDiscoverability) { (status, error) in
CKContainer.default().fetchUserRecordID { (record, error) in
CKContainer.default().discoverUserIdentity(withUserRecordID: record!, completionHandler: { (userID, error) in
print(userID?.hasiCloudAccount)
print(userID?.lookupInfo?.phoneNumber)
print(userID?.lookupInfo?.emailAddress)
print((userID?.nameComponents?.givenName)! + " " + (userID?.nameComponents?.familyName)!)
})
}
}
Use CKContainer.discoverUserIdentity(withUserRecordID:) in combination with CKContainer.fetchUserRecordID to get a CKUserIdentity object for the current user. You can then use a PersonNameComponentsFormatter to get their name from the nameComponents property of the identity.
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler { (recordId, error) in
if error != nil {
print("Handle error)")
}else{
self.container.discoverUserInfoWithUserRecordID(
recordId!, completionHandler: { (userInfo, error) in
if error != nil {
print("Handle error")
}else{
if let userInfo = userInfo {
print("givenName = \(userInfo.displayContact?.givenName)")
print("familyName = \(userInfo.displayContact?.familyName)")
}else{
print("no user info")
}
}
})
}
}