UID might be blank? - swift

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?

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,

How can I create a function to check if a user is blocked?

My app is being prepared for apple app store distribution and the last thing that I need to do is allow users to block another user. My idea was that in a user on firebase, there would be a collection called blocked and it would have documents with the name of blocked users, then whenever a user opens a profile, the app checks if the user of that profile is blocked, and if they are blocked, it just shows a title saying that the user is blocked, instead of showing the regular profile info.
Here is what the code that adds the blocked user to the list looks like:
public func BlockUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
.document(username)
ref.setData(["username": username])
}
And that works because this is what shows up in firestore:
So then to do the second part (checking if a profile user is in the blocked list) I use this code:
public var userIsBlocked: Bool = false
public func IsBlockedByUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
.document(username)
ref.getDocument { (document, error) in
if let document = document, document.exists {
self.userIsBlocked = true
} else {
print("Document does not exist")
self.userIsBlocked = false
}
}
}
However, even if a user is on the blocked list when I open their profile, it still prints "Document does not exist.". I used googles sample code to look for the document so why is it not finding it?
I still don't see an issue with the code I had previously used but I replaced it with a query and this seems to have fixed the issue. Here's the new code I'm using to search for if a user is blocked:
public var userIsBlocked: Bool = false
public func UserIsBlocked(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blocked")
ref.whereField("username", isEqualTo: username)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
self.userIsBlocked = false
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.userIsBlocked = true
}
}
}
}
public var blockedByUser: Bool = false
public func IsBlockedByUser(username: String) {
let currentUser = UserDefaults.standard.value(forKey: "username") as! String
let ref = database.collection("users")
.document(currentUser)
.collection("blockedBy")
ref.whereField("username", isEqualTo: username)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
self.blockedByUser = false
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.blockedByUser = true
}
}
}
}
And then when a user's profile is opened, these functions are called to check if the current user has either blocked the user whose profile they are trying to view or have been blocked by them. Based on the result, the two variables are set to either true or false. If they are both false, the profile loads, and if either of them is true, the profile does not load and shows an alert before returning to the previous view controller. This code is all over the place but it gets the job done!

Firebase Firestore not working in Swift Share Extension

I am building a Share Extension in Swift which saves a document to Firestore. So far I have been able to authenticate the correct user via keychain sharing and app groups. I can also get a documentID from a new document reference:
var ref = Firestore.firestore().collection("stuff").document()
print(ref.documentID) //prints the id
But when I try to save something to Firestore, nothing prints in the console, meaning I get neither a failure or success callback from Firebase (see below where I batch the updates). Here is my ShareController.swift file:
class ShareViewController: SLComposeServiceViewController {
var sharedIdentifier = "asdf"
override func viewDidLoad() {
FirebaseApp.configure()
setupKeychainSharing()
}
func setupKeychainSharing() {
do {
try Auth.auth().useUserAccessGroup(sharedIdentifier)
} catch let error as NSError {
}
}
override func isContentValid() -> Bool {
return true
}
override func didSelectPost() {
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
if let contents = content.attachments {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(m4aType) {
attachment.loadItem(forTypeIdentifier: m4aType, options: nil, completionHandler: { (results, error) in
if error == nil {
if let url = results as? URL {
if let audioData = NSData(contentsOf: url) {
let fileName = url.lastPathComponent
if Auth.auth().currentUser != nil {
guard let myId = Auth.auth().currentUser?.uid else { return }
let batch = Firestore.firestore().batch()
let ref = Firestore.firestore().collection("projects").document()
let project: [String: Any] = [
"ownerId": myId,
"type" : "audio",
"extensionUrl" : audioUrl.absoluteString
]
batch.updateData(project, forDocument: ref)
let privateRef = Firestore.firestore().collection("user-private").document(myId)
let privateUpdate: [String: Any] = [
"projects" : FieldValue.arrayUnion([ref.documentID])
]
batch.updateData(privateUpdate, forDocument: privateRef)
batch.commit(completion: { (error) in
if let error = error {
print("error updating database: \(error.localizedDescription)")
} else {
print("Database updated successfully!!!!!")
self.extensionContext!.completeRequest( returningItems: [], completionHandler: nil)
}
})
}
}
}
}
})
}
}
}
}
}
}
It appears you're trying to create additional documents within the projects node and update the user-private node. If so, the code in the question won't work.
UpdateData: Updates fields in the document referred to by document. If
document does not exist, the write batch will fail.
Here's a working batch write function that adds a document to a projects node with a Firestore generated document ID and child fields for extension, ownerId and type as well as a user_private collection with a document with a documentId of id_0.
func batchWrite() {
let batch = self.db.batch()
let ref = self.db.collection("projects").document()
let project: [String: Any] = [
"ownerId": "id_0",
"type" : "audio",
"extensionUrl" : "some url"
]
batch.setData(project, forDocument: ref)
let privateRef = self.db.collection("user-private").document("id_0")
let privateUpdate: [String: Any] = [
"projects" : "some projects"
]
batch.setData(privateUpdate, forDocument: privateRef)
batch.commit(completion: { (error) in
if let error = error {
print("error updating database: \(error.localizedDescription)")
} else {
print("Database updated successfully!!!!!")
}
})
}
Note that self.db is a class var reference to my Firestore. That makes it so you don't have to keep typing Firestore.firestore() and use self.db instead.
Also note that a batch is probably not needed in this case as it doesn't appear there are a significant number of writes occurring at the same time.
If you're not using batch, the .addDocument will add documents to collections.
Here's a function that writes a task to a tasks collection and auto-generates a documentId
func writeTask() {
let collectionRef = self.db.collection("tasks")
let data = [
"task": "some task"]
var ref: DocumentReference? = nil
ref = collectionRef.addDocument(data: data, completion: { err in
if let error = err {
print(error.localizedDescription)
}
print(ref?.documentID)
})
}
It is likely Firebase wasn't configured for your Share extension. Print statements do not work for share extension. Instead you should make use of NSLog statements i.e NSLog("refDocId:\(ref.DocumentId)"). Then in your xcode, navigate to Window > Devices and Simulators > Open Console. To configure Firebase in your Share extension, use FirebaseApp.configure()

Firebase Swift 3 Fetching multiple values from a group

I want to be able to list users in a tableview by fetching their UID Value from a key stored in my database. As of now, the code only fetches the first value in the database rather than all of the values. Here is the code for fetching the applicants.
func loadApplicants() {
let usersRef = ref.child("users")
usersRef.observe(.value, with: { (users) in
var resultArray = [UserClass]()
for user in users.children {
let user = UserClass(snapshot: user as! DataSnapshot)
if user.uid == self.job.userID {
let appRef = self.ref.child("jobs").child(self.job.postID).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
let sValue = snapshot.value
resultArray.append(user)
})
}
}
}) { (error) in
print(error.localizedDescription)
}
}
This is what my database looks like where the User's UIDs are stored.
jobs
"Job ID"
applicants:
-KtLJaQnFMnyI-MDWpys:"8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
-KtLLBFU_aVS_xfSpw1k:"GGqjtYvwSwQw9hQCVpF4lHN0kMI3"
If I was to run the app, it fetches UID: "8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
How can I implement a for loop or an if statement to ensure that all of the values are taken and appended into the table view
I know that I need a for loop before the fetchApplicants is called from AuthService because it is only fetching one UID but I can't work out where it would go.
Thanks.
P.S. This is what I have tried
func loadApplicants() {
let jobID = job.postID
let appRef = ref.child("jobs").child(jobID!).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let applicants = snapshot.value! as? [String:AnyObject] {
for (value) in applicants {
self.authService.fetchApplicants(applicantID: "\(value!)", completion: { (users) in
self.usersArray = users
self.tableView.reloadData()
})
}
}
})
}
but the output is:
(key: "-KtLLBFU_aVS_xfSpw1k", value: GGqjtYvwSwQw9hQCVpF4lHN0kMI3)
(key: "-KtLJaQnFMnyI-MDWpys", value: 8R6ZAojX0FNO7aSd2mm5aQXQFpk1)
Needed to use observe instead of observeSingleEvent
Answer:
appRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in

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

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