The following code once worked fine and I was able to save multiple values to a Firebase record. However this no longer works since upgrading to Swift 3 (Xcode 8). I now get the following error:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(setValue:) Cannot store object of type _SwiftValue at mood. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
The above error always mentions the second value, regardless of what type it is (even if it is one of the supported types like NSString). Here's what I have:
postsRef.childByAutoId().setValue(["postedBy": self.currentUser?.uid, "mood": mood, "status": status, "date": convertedDate])
This still seems to comply with the docs on the Firebase website. What am I doing wrong?
Thanks!
Try :-
let toBePosted = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
postsRef.childByAutoId().setValue(toBePosted){ (error, ref) -> Void in
}
As for converted date you will have to convert it to NSDate format when you retrieve it.
I just had this issue and after a few hours of toying around I think I've got a solution.
It looks like Firebase is wanting you to be very specific with your data types with Swift 3.
You need to straight up tell Swift what kind of dictionary you're going to have. In this case it's probably
let toBePosted : Dictionary<String, Any> = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
"Any" is a new thing in Swift 3 I think. AnyObject doesn't work here because Swift redefined what AnyObjects are, and Strings/Ints dont seem to be them anymore.
Finally, it seems like you have to have absolutely no optionals in your dictionary. Maybe an optional is a _SwiftValue? Once I got rid of optionals and garunteed each value was going to be there it started working for me.
This worked for me, let me know if you're still having an issue.
Related
I was trying to save my quiz data into firestore like so
db.collection("Quizzes").addDocument(data: ["Author": userEmail,
"quizTitle": quizTitle,
"quizDescription": quizDescription,
"quizDictionary": quizDictionary]) { (error) in
if let e = error{
print("There was an erorr saving the data to the Firestore \(e)")
}
after clicking the button that would perform this block of code my app crashes and the following error is printed
020-09-19 17:17:46.367783+0800 Quizzler[18001:782362] -[__NSCFNumber length]: unrecognized selector sent to instance 0xc4cd28a1a98b5156
2020-09-19 17:17:46.469546+0800 Quizzler[18001:782362] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber length]: unrecognized selector sent to instance 0xc4cd28a1a98b5156'
After doing some debugging, I found out that the "quizDictionary": quizDictionary part is what's causing the crash. Why is this happening? I thought that the method .addDocument(data: ) accepts [String: Any]?
If it is impossible to save Dictionary object into firestore, what are some workarounds for this?
If needed, this is my dictionary's declaration
dictionary declaration
Hey have a look here at the documentation for adding and managing data in fireStore :- https://cloud.google.com/firestore/docs/manage-data/add-data#swift.
Quick Overview line :- Using Map or Dictionary objects to represent your documents is often not very convenient, so Firestore supports writing documents with custom classes. Firestore converts the objects to supported data types.
Update :- As mentioned in comments it does not just convert any random object into a supported type. If you throw an NSImage at it, it won't know what to do with that. Or if the object contains some other random object it won't work. It has to contain supported types only. You can refer to this link to understand more about the supported data types.
You can only read and write supported data types with Firestore:
arrays
booleans
bytes
Firestore date objects
floating-point numbers
Firestore geographical objects
integers
maps (dictionaries, with string keys and values only of these supported types)
nil
Firestore references
strings
Therefore, you must convert your custom object to a format that is recognizable to Firestore which means replacing custom types with types that are primitive to the database.
https://firebase.google.com/docs/firestore/manage-data/data-types
I tried googling this problem but it seems like everyone has the opposite problem where the app runs on simulator but not their device. I've been struggling a LOT all week with firebase asynchronous calls returning null and linked the issue to persistence being enabled. All my problems go away if I disable persistence, but I want it enabled. I learned recently about synchronous issues with the different listeners/persistence and have been struggling with firebase returning outdated/nil values for a while.
Simulator was working just a week or two ago and I'm not sure what's changed. I've tried messing with / switching out .observeSingleEvent for .observe and still crashes at this code:
let synced = ref.child("profiles").child((FIRAuth.auth()?.currentUser?.uid)!).child("level")
synced.observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
print(snapshot)
print(snapshot.ref)
if (snapshot.value as! String == "One") {
........//CRASH
With the message:
Could not cast value of type 'NSNull' (0x10b7cf8c8) to 'NSString' (0x10a9dfc40).
When I try to print snapshot, it shows me an empty snapshot. But when I print the ref, the link works and takes me to the right place in my db (where I can see the data exists)
Any ideas how to fix/get around this without disabling persistence? Or more importantly I guess, should I care that it doesn't work in simulator if it works on a device? Will this matter for app store approval / affect future users?
If you'd like to see for yourself that this is an issue of firebase getting a nil/outdated value when the reference exists, here is what I see when I follow the printed ref link
The error seems fairly explicit: there is no value, so you can't convert it to a string.
synced.observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
if (snapshot.exists()) {
if (snapshot.value as! String == "One") {
........//CRASH
I am currently going through the processes of migrating swift 2.3 to 3 using the most updated Kinvey SDK (version 3.3.5). They have done a ton of updates since the 1x versions. My question is has anyone successfully been able to query on the PersistableKeyID field and pull multiple objects?
I use to be able to use the "loadObjects" function which would take an array of strings as an argument. This function has since been depreciated and replaced with find(byId). See below:
dataStore.find(byId: "only takes one") { uClass, error in
if let uClass = uClass {
//succeed
print("UClass: \(uClass)")
} else {
//fail
}
The issue is, it will only take a single string as an argument. I have attempted to use the query functionality, but I cannot get it to take the "_id" field as a parameter. Using the following code:
//Just statically creating the sectionID array for now. This will dynamically be created
testIDs = ["58668307206c11177e5ab0d4", "58668307206c11177e5ab0d4", "57ad00a505a2bb55632659c3"]
let sectionStore = DataStore<Section>.collection()
let sectionQuery = Query(format: "_id IN %#", testIDs)
sectionStore.find(sectionQuery) {sectionResult, error in
if let sectionResult = sectionResult {
self.sectionsTest = sectionResult
self.sectionCollectionView.reloadData()
} else{
//Error
}
}
I receive the error:
'Invalid property name', reason: 'Property '_id' not found in object of type 'Section'
Anyone have an idea on how to perform this now that "loadObjects" has been depreciated? There is no delivered "find(byIds)" that I could find.
Jbone107,
I was able to get results with this, let me know if the below works for you.
let id:[String] = ["5855026650a816ec29012908","5855024a21400c5b492bea20"]
let query = Query(format: "_id IN %#", id)
dataStore.find(query) { data, error in
if let data = data {
//succeed
print(“Data: \(data)")
} else {
//fail
print("fetching failed")
}
}
Thanks,
Pranav,
Kinvey
Answered: Per the Data Store Guide for iOS, by default the ".collection()" is of type "cache". The "Cache" type will store data locally. This must be why "Realm" is now included with the version 3x SDK.
I updated my DataStore collection to:
let sectionStore = DataStore<Section>.collection(.network)
I added ".network" to force the query to pull from the backend rather than the cache file. This actually identified "_id" as a property and the query worked successfully. For some reason the "cache" file isn't storing this as a property.
Additional SDK Question Answered
I was having an issue pulling NSNumber from the Kinvey backend. This ended up being a similar issue related to the "cache" query. I reviewed the Realm support site as a last resort effort to try and figure this out. I found that Realm doesn't actually support type "NSNumber".
Excerpt taken from: https://realm.io/docs/swift/latest/
Realm supports the following property types: Bool, Int8, Int16, Int32, Int64, Double, Float, String, NSDate, and NSData.
Unfortunately, Kinvey doesn't support "Int" types. As a work around, I have changed them to string and am just converting back to "Double" or another type after I pull the data. However, if I just use ".network" collection types, then NSNumber still works.
Thanks,
James
I use SKProductsRequest to download product infos from App Store.
When I test a connectivity loss on my device, the request fails, but my app crashes within the SKRequestDelegate when I try to NSLog the error:
What am I doing wrong ? Another curious thing to me is that Expression Inspector is able to display NSError.debugDescription...
It fails on the first request, so there is no possible bug relative to multiple uses of productRequest variable (which is a strong ref in my swift class).
I finally found the reason. It is not related to SKProductsRequest!
I think there is a nasty bug with NSLogand string interpolation because when I replace:
NSLog("Failed: \(error.debugDescription)")
by
print("Failed: \(error.debugDescription)")
all is fine!
Apparently, the content of the error message can provoke a EXC_BAD_ADDRESS in NSLog (even without string interpolation in fact: NSLog(error.debugDescription) fails too).
Related anwser: https://stackoverflow.com/a/29631505/249742
NSLog("%#", error.debugDescription)
seems to work fine in every cases.
Perhaps NSLog(variable) is a misuse of NSLog, but I think NSLog(\(variable)) should be interpreted like NSLog("%#", variable). Else, there is no reliable way to interpolate strings with NSLog using the swift way \().
I have created an iOS app using Swift and everything is working fine and dandy on the simulator. I get no errors or crashes at all, but when I submit my app to put up on the app store Apple rejects it and lets me know that it crashes when the user makes a selection. I cannot recreate this error/crash. I took the crash logs and symbolicated them. This line of code came up as the culprit for the crashes:
linksToPass = getLinks(season) as [String:[String]]
This line is trying to store the resulting Dictionary from the getLinks() function I created. It for sure is getting a dictionary and if there is no dictionary to send back I create a dictionary which has error information in it, so it is for sure returning a dictionary in that format no matter what. Seeing as I cannot recreate the crash, I am just trying to error check this line of code in any way possible so it does't crash when I resubmit to Apple.
I tried checking if the resulting dictionary was nil like so:
if(getLinks(seasons) != nil){
linksToPass = getLinks(season) as [String:[String]]
}
This is not valid though, and XCode lets me know that UInt8 is not compatible with NSDictionary or something of that nature.
I then fixed that line and changed it to this:
if(getLinks(seasons) != ["":[""]]){
linksToPass = getLinks(season) as [String:[String]]
}
I am just not sure if this is even a good way to check for errors. I was wondering if there were any suggestions on how I may go about making sure this line does not fail and result in a crash. Thank you very much.
EDIT:
Here is my getLinks() function if that helps add more info to the problem:
var season = ""
let hymn_links = Hymn_Links()
func getLinks (nameofseason:String) -> NSDictionary
{
switch (nameofseason)
{
default:
return ["Maps Not Found": []]
}
}
EDIT #2:
This is my updated getLinks() function with the use of optionals.
func getLinks (nameofseason:String) -> NSDictionary?
{
switch (nameofseason)
{
default:
return nil
}
}
Also in my statement of linksToPass I changed it to:
if let links = getLinks(season) as? [String:[String]]
{
linksToPass = links
hymnnames = [String] (linksToPass.keys)
}
There are some known issues with the Swift optimiser. Some people have resorted to shipping with debug builds.
My suggestion would be to test with an optimised build to see if you can reproduce it. You can then try shipping a debug build to the App store.
General Code Comments
Why are you returning an NSDictionary rather than a Swift dictionary anyway? Without knowing the contents and creation method for your hymn_links object I can't be sure how good it is.
I would avoid as casts until Swift 1.2 and stick to using as? and then handling the nil case. At least in your "Edit 2" a nil will cause a crash as nil cannot be cast to [String:[String]] although [String:[String]]? should be possible.
Can you guarantee that all of the items returned by the switch statement will never under any circumstances be nil? If not getLinks should return an Optional.
Note that is is virtually impossible for getLinks to know that one of the items will never be nil and in Swift un-handed nils are a crash waiting to happen. Unless all these methods correctly handle nil.
Return an Optional and handle that in the statement that calls getLinks.
Languages handle nils differently, Objective-C handles them rather well, Java and Swift by crashing. But Swift has a mechanism to handle nils without crashing: Optionals, use it.