Swift: Proper Firebase error-handling - swift

I'm not able to understand why neither print lines are executed when running the following code:
ref.child("schools/\(schoolTextField.text!)/settings/pin").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value)
}, withCancel: { (error) in
print(error.localizedDescription)
})
It used to work, until I made some changes regarding firebase authentication, but I don't see why the withCancel block is not executed!
How do I catch whatever error is occuring here? No error-message is printed in the log.
EDIT:
I found a similar question here which suggests that the problem might be related to the authentication after all. In appDelegate's didFinishLaunchingWithOptions I check if the user auth-token exists in Firebase Auth:
Auth.auth().currentUser?.getIDTokenForcingRefresh(true, completion: { (response, error) in
guard error == nil, let uid = response else {
print(error)
return
}
...
})
In current case there is an error, which is printed:
Error Domain=FIRAuthErrorDomain Code=17011 "There is no user record corresponding to this identifier. The user may have been deleted."
I'm now also getting these errors printed in the log:
[Common] _BSMachError: port b43b; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
[Common] _BSMachError: port b43b; (os/kern) invalid name (0xf) "Unable to deallocate send right"

Try this way:
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print("SNAP: \(snap)")
}
Also The error code 17011 is a user not found error see FIRAuthErrorCode for more info also it's better you create a Dataservice class to handle your interaction with firebase see Creating A Reusable Firebase Data Service for more info.

Related

Delete document from cloud firestore

I am trying to implement a function that allows the user to delete chats that they have with other users.
At the moment the code works and the firestore document is deleted successfully however as soon as the delete happens the code crashes and i get this error "Fatal error: Unexpectedly found nil while unwrapping an Optional value" next to the id = data!["id"] in the code. I guess that after the delete happens firestore keeps listening for documents and finds an empty collection following the delete. Does anyone know how to stop this from happening?
public func deleteConversation(conversationId: String, completion: #escaping (Bool) -> Void) {
// Get all conversations for current user
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(test!)
print("deleting conversation with \(test!)")
ConversationRef.addSnapshotListener { snapshot, error in
guard let document = snapshot else {
print("Error getting documents: \(String(describing: error))")
return
}
let data = document.data()
if let id = data!["id"] as? String, id == conversationId {
print("conversation found")
ConversationRef.delete()
}
completion(true)
print("deleted conversation")
}
}
The problem comes from:
ConversationRef.addSnapshotListener { snapshot, error in
By calling addSnapshotListener you're adding a listener that:
Gets the document snapshot straight away,
Continues listening for changes and calls your code again then there are any.
The problem is in #2, as it means your code executes again when the document is deleted, and at that point document.data() will be nil.
The simplest fix is to only read the document once:
ConversationRef.getDocument { (document, error) in

CloudKit was Working but Getting a Permission Error All of a Sudden

I had CloudKit working for the last couple weeks and this morning when I was trying to save a record, something I had done a number of times before, I started getting a Permission Error
Error saving to CloudKit: <CKError 0x600001f48ed0: "Permission Failure"
(10/2007); server message = "Operation not permitted"; uuid = 78FA3DD1-
EA44-4701-9A7E-8291F076DD8F; container ID = "[CloudKit Container Name]"> - Error fetching auth tokens from server:
Operation not permitted'
So I have triple checked that it's requesting to the right container, which it is (I only have two containers setup on my CloudKit so it's an easy check.
I have turned off and turned CloudKit on again in XCode as an attempt to force reset it (not sure if that did anything helpful but figured I would give it a shot)
In case this is needed, this is how I am saving to CloudKit (had no problem with this bit of code in the past.
func createRecord(title: String, type: String, comment: String) {
let audioRecord = CKRecord(recordType: "Audio")
audioRecord["title"] = title as CKRecordValue
audioRecord["type"] = type as CKRecordValue
audioRecord["comment"] = comment as CKRecordValue
let audioURL = audioRecorder.getAudioURL()
let audioAsset = CKAsset(fileURL: audioURL)
audioRecord["audio"] = audioAsset
DispatchQueue.main.async {
CKContainer.default().publicCloudDatabase.save(audioRecord) { [self] record, error in
//print(CKContainer.default())
if let error = error {
print("Error saving to CloudKit: \(error.self) - \(error.localizedDescription)")
} else {
print("Record has been successfully saved to CloudKit")
}
}
}
}

How To Fetch Error Code From Firebase?

Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!)
{ (user, error) in
if error != nil {
print(error!)
self.warningLabel.isHidden = false;
self.passwordTextField.text = "";
} else {
print("Log in succesful")
self.performSegue(withIdentifier: "welcomeSeg", sender: self)
}
}
Whenever I sign in or sign up a user I just print a generic warning label instead of the actual issue. I print the error I receive and it's too verbose to show to the user.
Error Domain=FIRAuthErrorDomain Code=17009 "The password is invalid or the user does not have a password." UserInfo={NSLocalizedDescription=The password is invalid or the user does not have a password., error_name=ERROR_WRONG_PASSWORD}
Error Domain=FIRAuthErrorDomain Code=17008 "The email address is badly formatted." UserInfo={NSLocalizedDescription=The email address is badly formatted., error_name=ERROR_INVALID_EMAIL}
Is there any way to fetch the error code so I can be more specific with my error messages? I've looked through the documentation but have been unsuccessful in coming up with anything.
I would recommend creating an AuthErrorCode object (provided by the Firebase SDK) from the error you receive and using that as you see fit. If I remember correctly, AuthErrorCode is an enum with cases like .wrongPassword, .invalidEmail, etc.
A simple pseudocode example:
if error != nil {
if let error = AuthErrorCode(rawValue: error?.code) {
switch error {
case .wrongPassword:
// do some stuff with the error code
}
}
Also, I feel your pain. I've found that the Swift SDK documentation lags quite a bit when changes come along.

Realm Object Server Swift, changing default permission of a realm

I created a realm with an admin account and when i look at my dashboard my realm is there. Its owner column is blank though? Is it normal? Because I opened that synced realm with my admin account.
My main question is this, in default permissions it says "no access". I tried to give all users permission to write in that realm shown in below:
SyncUser.logIn(with: admin, server: serverURL) { (user, error) in
let permission = SyncPermissionValue(realmPath: "realm://myServerIp/swipeItApp/", username: "*", accessLevel: .write)
user?.applyPermission(permission, callback: { (error) in
if error != nil {
print(error?.localizedDescription)
} else {
print("success")
}
})
}
but neither error or success prints. What is wrong in my code? Thanks!
Thanks for using Realm.
The reason you are not seeing the callback is because you are running the applyPermission() method directly in the logIn() callback. The applyPermission() method has to be run on a thread with an active run loop, and the logIn() callback runs on threads without run loops managed by a background queue.
To fix this problem, use DispatchQueue.main.async to dispatch your code back onto the main queue:
SyncUser.logIn(with: admin, server: serverURL) { (user, error) in
let permission = SyncPermissionValue(realmPath: "realm://myServerIp/swipeItApp/", username: "*", accessLevel: .write)
DispatchQueue.main.async {
user?.applyPermission(permission, callback: { (error) in
if error != nil {
print(error?.localizedDescription)
} else {
print("success")
}
})
}
}
We consider the need to use DispatchQueue.main.async for this use case a design mistake and will be changing the way logIn() works in our upcoming Realm 3.0.0 release to run the callback on the main queue by default. So if you upgrade to Realm 3.0.0 once it's released you will no longer need to use the workaround I detailed above; your original code should work as-is.

CloudKit Error: Change Token Expired, Reset Needed

Swift 3.1, Xcode 8.3.3
I keep getting an error from CloudKit and I don't know what to do about it.
I'm tracking notifications from CloudKit like this:
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: previousChangeToken)
//Hold the notification IDs we processed so we can tell CloudKit to never send them to us again
var notificationIDs = [CKNotificationID]()
operation.notificationChangedBlock = { [weak self] notification in
guard let notification = notification as? CKQueryNotification else { return }
if let id = notification.notificationID {
notificationIDs.append(id)
}
}
operation.fetchNotificationChangesCompletionBlock = { [weak self] newToken, error in
if let error = error{
print(error) //<-- <!> This is the error <!>
}else{
self?.previousChangeToken = newToken
//All records are in, now save the data locally
let fetchOperation = CKFetchRecordsOperation(recordIDs: recordIDs)
fetchOperation.fetchRecordsCompletionBlock = { [weak self] records, error in
if let e = error {
print("fetchRecordsCompletionBlock Error fetching: \(e)")
}
//Save records to local persistence...
}
self?.privateDB.add(fetchOperation)
//Tell CloudKit we've read the notifications
let operationRead = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDs)
self?.container.add(operationRead)
}
}
container.add(operation)
And the error says:
<CKError 0x174241e90: "Change Token Expired" (21/1016); server message
= "Error code: RESET_NEEDED"; uuid = ...; container ID = "...">
The CKServerChangeToken documentation don't mention anything about resetting the token, and the CloudKit dashboard doesn't offer any such option.
Any idea what I'm supposed to do?
This error code is CKErrorCodeChangeTokenExpired, and it's an indication that you need to re-sync your changes.
https://developer.apple.com/documentation/cloudkit/ckerror/2325216-changetokenexpired
This error code gets returned when the change token is too old, or the container has been reset (resetting the container invalidates old change tokens).
The comments related to this error code include:
(Describing the code itself):
The previousServerChangeToken value is too old and the client must re-sync from scratch
(On various fetch operation completion/updated blocks):
If the server returns a CKErrorChangeTokenExpired error, the serverChangeToken used for this record zone when initting this operation was too old and the client should toss its local cache and re-fetch the changes in this record zone starting with a nil serverChangeToken.