How to fix error when looking for child in firebase database (Swift)? - swift

I am trying to save items to a Firebase database under a child taken from a text field. I haven't got to that yet though, I am still trying to work out how to check if a value already exists. Here is my code...
#objc func submitNote() {
print("Attempting to save or update note")
//Check if file exists
if name != nil && text != nil {
//Access database
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("lists").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(self.name!){
print("true")
//Update
return
} else {
print("false")
//Create
self.uploadNew()
}
})
return
} else {
print("Error")
return
}
}
It has to be an #objc function as it is run using #selector in a menu bar. As you can see it checks to make sure the text fields aren't empty. Here are the variables...
public var name: String?
public var text: String?
Which are set inside separate functions...
name = titleText.text
text = textView.text
The reason I have set the variables this way is because I am creating the view programically and both individual text views/fields are set inside functions so I don't think I can access them from the submitNote function. Anyway the error I'm getting it this...
*** Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(hasChild:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''
I have checked and triple checked but there aren't any of those values in the text fields and even though the if statement is meant to stop the app continuing unless they are not equal to nil I tried with text in the fields but no matter what if I press the button to run this function I get the same error. Does anyone know how to fix it?

You only check if name is not null but not if it is empty ("")
if let value = self.name, !value.isEmpty...

Your Firebase structure was not included in the question so I'll make one up for you
lists
Leroy: true
Billy: true
James: true
now some code to see if a name exists in the lists node. We're using snapshot.exists to see if any data was returned in the snapshot.
func doesNameExistInLists(name: String) {
let ref = self.ref.child("lists").child(name)
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("found it")
} else {
print("name not found")
}
})
}
then when it's called, here are the results
self.doesNameExistInLists(name: "Billy")
console: found it
self.doesNameExistInLists(name: "aaaa")
console: name not found

Related

Always got Unexpectedly found nil while unwrapping an Optional value inside of CallDirectoryHandler

I added target Call Directory Extension to app, made a group, put main app and extension there, share appname.xcdatamodeld with main app and extension and create CoreDataHandler class to access CoreData with group and appname.sqlite
Then I made interface in main app to add phone numbers and save them in Int64 format. It works fine - numbers saved. And I can get them inside main app like:
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "NumberList")
do {
let entries = try managedContext.fetch(fetchRequest)
for item in entries {
print(item.value(forKeyPath: "number") as! Int64)
}
}
catch {}
All works, numbers printed... BUT inside CallDirectoryHandler with absolutely same situation I always got Fatal error: Unexpectedly found nil while unwrapping an Optional value, when Call Directory Extension reloads.
What am I do wrong? And actually it all works fine BEFORE update to last Xcode and IOS version (13.4)
private func addOrRemoveIncrementalBlockingPhoneNumbers(to context: CXCallDirectoryExtensionContext, since date: Date) {
print(numbers.count) //<-- It shows numbers count properly!
for entity in self.numbers {
print(entity) //<-- It shows entity!
let number: Int64? = entity.value(forKeyPath: "number") as? Int64
print(entity.value(forKey: "number")!) //<-- But fall here with nil, but attribute exists in main app!
let isHidden: Bool? = (entity.value(forKeyPath: "is_hidden") as? Bool)!
if isHidden != nil { context.addBlockingEntry(withNextSequentialPhoneNumber: number!) } else { context.removeBlockingEntry(withPhoneNumber: number!) }
}
}

Ambiguous reference to member 'save(_:completionHandler:)' with CloudKit save attempt

I'm trying to save back to CloudKit after updating a reference list and getting the error on the first line of this code block.
Error: Ambiguous reference to member 'save(_:completionHandler:)'
CKContainer.default().publicCloudDatabase.save(establishment) { [unowned self] record, error in
DispatchQueue.main.async {
if let error = error {
print("error handling to come")
} else {
print("success")
}
}
}
This sits within a function where the user going to follow a given location (Establishment). We're taking the existing establishment, and its record of followers, checking to see if the selected user is in it, and appending them to the list if not (or creating it if the list of followers is null).
Edit, in case helpful
//Both of these are passed in from the prior view controller
var establishment: Establishment?
var loggedInUserID: String?
#objc func addTapped() {
// in here, we want to take the logged in user's ID and append it to the list of people that want to follow this establishment
// which is a CK Record Reference
let userID = CKRecord.ID(recordName: loggedInUserID!)
var establishmentTemp: Establishment? = establishment
var followers: [CKRecord.Reference]? = establishmentTemp?.followers
let reference = CKRecord.Reference(recordID: userID, action: CKRecord_Reference_Action.none)
if followers != nil {
if !followers!.contains(reference) {
establishmentTemp?.followers?.append(reference)
}
} else {
followers = [reference]
establishmentTemp?.followers = followers
establishment = establishmentTemp
}
[this is where the CKContainer.default.....save block pasted at the top of the question comes in]
I've looked through the various posts on 'ambiguous reference' but haven't been able to figure out the source of my issue. tried to explicitly set the types for establisthmentTemp and followers in case that was the issue (based on the solutions to other related posts) but no luck.
Afraid I'm out of ideas as a relatively inexperienced newbie!
Help appreciated.
Documenting the solution that I figured out:
Combination of two issues:
I was trying to save an updated version of a CK Record instead of updating
I was not passing a CK Record to the save() call - but a custom object
(I believe point two was the cause of the 'ambiguous reference to member'
error)
I solved it by replacing the save attempt (first block of code in the question) with:
//first get the record ID for the current establishment that is to be updated
let establishmentRecordID = establishment?.id
//then fetch the item from CK
CKContainer.default().publicCloudDatabase.fetch(withRecordID: establishmentRecordID!) { updatedRecord, error in
if let error = error {
print("error handling to come")
} else {
//then update the 'people' array with the revised one
updatedRecord!.setObject(followers as __CKRecordObjCValue?, forKey: "people")
//then save it
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in
}
}
}

Removing one item from map removes all items

I have a simple Map in my Firestore Database, looks like that:
I'm trying to remove just one item from the following map, like that:
func removeUserFromFriendsList(friendToRemove id: String, _ currentUserId: String, completion: #escaping(Bool)->()) {
let friendsRef = db.collection(USERS_COLLECTION).document(currentUserId)
friendsRef.updateData([
USER_FOLLOWING: FieldValue.arrayRemove([id])
]) { (error) in
if error != nil {
completion(false)
}
completion(true)
}
}
but instead of removing the item with the ID I got, it removes the whole list inside following
I guess the problem was that I was in the following level, and didn't specify the id to delete correctly. so It just deleted everything inside following.
Specifying the id and deleting it like that did the trick:
func removeUserFromFriendsList(friendToRemove id: String, _ currentUserId: String, completion: #escaping(Bool)->()) {
let friendsRef = db.collection(USERS_COLLECTION).document(currentUserId)
friendsRef.updateData([
USER_FOLLOWING + "." + id : FieldValue.delete()
]) { (error) in
if error != nil {
completion(false)
}
completion(true)
}
}
USER_FOLLOWING is the name of the field in the database (aka, 'following').
Adding the + "."+ and the id just specify which id we want to delete, so it looks likes that:
following.AP3ENXgW2mhvaWsUeDOxchYaAGm1 <--- the field and id to delete
and then using FieldValue.delete to delete it entirely.
EDIT:
Answer found here:
How to remove an array item from a nested document in firebase?
Following the example from the Firebase documentation:
let friendsRef = db.collection(USERS_COLLECTION).document(currentUserId)
// Atomically removes an ID from the "USER_FOLLOWING" array field.
friendsRef.updateData([
"USER_FOLLOWING": FieldValue.arrayRemove([id])
])
If this is not an option for you, a workaround for this would be to read the entire array out of the document, make modifications to it in memory, then update the modified array field entirely.

Workaround for EKParcipant URL accessing crash?

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}
This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.
This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.
This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}
This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}
Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.