How to update a document in firebase firestore using swift? - ios

when i run the following code i get this error.
['FIRESTORE INTERNAL ASSERTION FAILED: Invalid document reference. Document references must have an even number of segments, but User has 1']
func saveLabel(uid: String, label: String){
let userRef = db.collection("User").document(uid)
userRef.updateData(["label": label]) { (error) in
if error == nil {
print("updated")
}else{
print("not updated")
}
}
}

This error almost certainly means that uid is empty. You should log it to make sure.

Related

Listener event not triggered when document is updated (Google Firestore)

I am struggling to understand why my event listener that I initialize on a document is not being triggered whenever I update the document within the app in a different UIViewController. If I update it manually in Google firebase console, the listener event gets triggered successfully. I am 100% updating the correct document too because I see it get updated when I update it in the app. What I am trying to accomplish is have a running listener on the current user that is logged in and all of their fields so i can just use 1 global singleton variable throughout my app and it will always be up to date with their most current fields (name, last name, profile pic, bio, etc.). One thing I noticed is when i use setData instead of updateData, the listener event gets triggered. For some reason it doesn't with updateData. But i don't want to use setData because it will wipe all the other fields as if it is a new doc. Is there something else I should be doing?
Below is the code that initializes the Listener at the very beginning of the app after the user logs in.
static func InitalizeWhistleListener() {
let currentUser = Auth.auth().currentUser?.uid
let userDocRef = Firestore.firestore().collection("users").document(currentUser!)
WhistleListener.shared.listener = userDocRef.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("INSIDE LISTENER")
}
}
Below is the code that update's this same document in a different view controller whenever the user updates their profile pic
func uploadProfilePicture(_ image: UIImage) {
guard let uid = currentUser!.UID else { return }
let filePath = "user/\(uid).jpg"
let storageRef = Storage.storage().reference().child(filePath)
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
storageRef.putData(imageData) { metadata, error in
if error == nil && metadata != nil {
self.userProfileDoc!.updateData([
"profilePicURL": filePath
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
}
}
You can use set data with merge true it doesn't wipe any other property only merge to specific one that you declared as like I am only update the name of the user without wiping the age or address
db.collection("User")
.document(id)
.setData(["name":"Zeeshan"],merge: true)
The answer is pretty obvious (and sad at the same time). I was constantly updating the filepath to be the user's UID therefore, it would always be the same and the snapshot wouldn't recognize a difference in the update. It had been some time since I had looked at this code so i forgot this is what it was doing. I was looking past this and simply thinking an update (no matter if it was different from the last or not) would trigger an event. That is not the case! So what I did was append an additional UUID to the user's UID so that it changed.

saveInBackground (Parse) throwing an error

I am having an issue in the following swift code, using Parse-Server to save data online.
let xxDico = ["N8": "Eight", "N5": "Five"],
upLoadParseUnit = PFObject(className: "numberClass",
dictionary: xxDico)
print("xxDico : \(xxDico.count)")
upLoadParseUnit.saveInBackground {
(succeeded:Bool, error:Error?) in
if succeeded {
print("\(#function) succeeded!!")
} else {
if error?.localizedDescription == "NAME-USED" {
// A record previously existed with the same name.
print("\(#function) did not succeeded!!\nError: \(error?.localizedDescription)")
return
}
print("Error(1) in \(#function)\n" + (error?.localizedDescription)!)
}
}
And this is what I get in the debugging console:
xxDico : 2
Error(1) in executeUpLoad(_:)
The data couldn’t be read because it isn’t in the correct format.
This seems simple, but what am I doing wrong to get this error?
And what am I supposed to do?

Memory leak using Firebase

I execute an API call in Firebase for retrieving the user profile information and storing it in a ViewController member variable.
The API is declared as a static function inside a class MyApi:
// Get User Profile
static func getUserProfile(byID userId:String,response:#escaping (_ result:[User]?,_ error:Error?)->()) {
// check ID is valid
guard userId.length > 0 else {
print("Error retrieving Creator data: invalid user id provided")
response(nil,ApiErrors.invalidParameters)
return
}
// retrieve profile
let profilesNode = Database.database().reference().child(MyAPI.profilesNodeKey)
profilesNode.child(userId).observe(.value, with: { (snapshot) in
// check if a valid data structure is returned
guard var dictionary = snapshot.value as? [String:AnyObject] else {
print("Get User Profile API: cannot find request")
response([],nil)
return
}
// data mapping
dictionary["key"] = userId as AnyObject
guard let user = User(data:dictionary) else {
print("Get User Profile API: error mapping User profile data")
response(nil,ApiErrors.mappingError)
return
}
response([user], nil)
}) { (error) in
response(nil,ApiErrors.FirebaseError(description: error.localizedDescription))
}
}
and I call it like that:
MyAPI.getUserProfile(byID: creatorId) { (profiles, error) in
guard let profiles = profiles, profiles.count > 0 else {
Utility.showErrorBanner(message: "Error retrieving Creator profile")
print("Error retrieving creator profile ID:[\(creatorId)] \(String(describing: error?.localizedDescription))")
return
}
self.currentProfile = profiles.first!
}
The ViewController is called in Modal mode so it should be deallocated every time I exit the screen.
Problem: a huge chunk of memory get allocated when I enter the screen, but it doesn't get freed up when I leave it. I'm sure about this because the problem doesn't appear if I remove the line self.currentProfile = profiles.first! (obviously)
How can I avoid this from happening?
NOTE: currentProfile is of type User, which was used to be a struct. I made it a class so I could use a weak reference for storing the information:
weak var currentCreator: User? {
didSet {
updateView()
}
}
but the problem still persists.
You are adding an observer:
profilesNode.child(userId).observe(...)
But you never remove it. As long as that observe is still added, it will hold on to memory from the entire set of results, and continually retrieve new updates. It's a really bad practice not to remove your observers.
If you want to read data just a single time, there is a different API for that using observeSingleEvent.

CloudKit: Get users firstname/surname

I'm trying to get the users first name using cloud kit however the following code is not getting the users first name and is leaving firstNameFromFunction variable empty. Does anyone know how to achieve this in iOS 10?
let container = CKContainer.default()
container.fetchUserRecordID { (recordId, error) in
if error != nil {
print("Handle error)")
}else{
self.container.discoverUserInfo(
withUserRecordID: 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)")
firstNameFromFunction = userInfo.displayContact?.givenName
}else{
print("no user info")
}
}
})
}
}
the permission screen that comes up when asking for the first time, IMO, is very poorly worded. They need to change that. It says "Allow people using 'your app' to look you up by email? People who know your email address will be able to see that you use this app." This make NO sense. This has nothing to do with asking the user to get their iCloud first name, last name, email address.
Speaking of email address - this and the phone number from the lookupInfo property is missing - i.e. set to nil, even though those values are legit and correct. Filing a bug tonight.
First, you will need to request permission to access the user's information.
Then, you can use a CKDiscoverUserIdentitiesOperation. This is just like any other CKOperation (eg. the modify record operation). You just need to create a new operation with the useridentitylookupinfo. Then you will also need to create a completion block to handle the results.
Here is an example function I created:
func getUserName(withRecordID recordID: CKRecordID,
completion: #escaping (String) -> ()) {
if #available(iOS 10.0, *) {
let userInfo = CKUserIdentityLookupInfo(userRecordID: recordID)
let discoverOperation = CKDiscoverUserIdentitiesOperation(userIdentityLookupInfos: [userInfo])
discoverOperation.userIdentityDiscoveredBlock = { (userIdentity, userIdentityLookupInfo) in
let userName = "\((userIdentity.nameComponents?.givenName ?? "")) \((userIdentity.nameComponents?.familyName ?? ""))"
completion(userName)
}
discoverOperation.completionBlock = {
completion("")
}
CKContainer.default().add(discoverOperation)
} else {
// iOS 10 and below version of the code above,
// no longer works. So, we just return an empty string.
completion("")
}
}
First you need to ask the user for permission to be discovered.
Use CKContainer.default().requestApplicationPermission method passing .userDiscoverability on applicationPermission parameter.
The CKContainer.default().discoverUserInfo method is deprecated on iOS 10. Instead use CKContainer.default().discoverUserIdentity method.
Do something like:
CKContainer.default().requestApplicationPermission(.userDiscoverability) { (status, error) in
CKContainer.default().fetchUserRecordID { (record, error) in
CKContainer.default().discoverUserIdentity(withUserRecordID: record!, completionHandler: { (userIdentity, error) in
print("\(userIdentity?.nameComponents?.givenName)")
print("\(userIdentity?.nameComponents?.familyName)")
})
}
}

How to check for Firebase permission denied?

How do I check for "permission_denied" in my swift code?! so that I can display a proper alert to user?
self.ref = Database.database().reference(withPath: "PlayerBoxes")
//
handle = ref.child(pID).observe(.value, with: { snapshot in
// Do Something
}) { (error) in
print(error.localizedDescription)
}
// Update info into Firebase, do not overwrite entire node
ref.child(self.pID).updateChildValues(sqr.toDictionary()) <-- Permission Denied
You need to use updateChildValues(withCompletionBlock:) instead.
This returns a database reference along with any raised error.
ref.child(self.pID).updateChildValues(sqr.toDictionary()) { (error, reference) in
if error != nil
{
// handle the error
print(error!.localizedDescription)
}
// you're fine, no error raised
}

Resources