Firebase - updateChildValues crashes app when setting key in dictionary from constant - ios

All I am trying to accomplish is setting the user's email string as a key in my database. My issue seems to be that the updateChildValues method crashes whenever I set the dictionary using a key that I have assigned to a constant - In this case userEmail. If I change the key to a string literal it works just fine, but that is not how I intend to structure this database node.
guard let uid = Auth.auth().currentUser?.uid, let email = Auth.auth().currentUser?.email else {
// handle
return
}
let groupId = UUID().uuidString
let userGroupIdValue = ["groupId": groupId]
let groupName = ["groupName": text]
let userEmail = [email: 1]
Database.database().reference().child("groups").child(groupId).child("memberEmails").updateChildValues(userEmail, withCompletionBlock: { (error, reference) in
// handle errors
})
If I change the userEmail dictionary to ["someText": 1] it works just fine, as do the constants userGroupIdValue and groupName. Through my troubleshooting, it seems as though this method does not like the fact that the email property in Auth.auth().currentUser? was previously an optional, even though I am using a guard statement. I have used if let statements to unwrap as well and it still crashes. Could this be a bug of sorts?

Well firebase support got back to me and it seems the issue is the email address that was unwrapped contained a "." in the string, and this character is not allowed to be used in a key in the database along with several other characters. Super frustrating but at least I have an answer.

Related

Why is firebase function only fetching once?

I have a firebase fetch function which I call inside of a for loop. In it, I pass in variables postID and uid.
for child in snapshots.reversed() {
let keyValue = child.key
let uid = keyValue.split(separator: ":")[0]
let postIDDoubleVal = keyValue.split(separator: ":")[1]
print(String(uid), " This is the uid!!!!!!")
print(postIDDoubleVal, " This is tfdsafdsafdsafdsafads4!!!!")
self.fetchUsersPost(uid: String(uid), postID: "post:\(postIDDoubleVal)")
}
There are currently 2 users who's UIDs ARE successfully looped over.
The problem arrises when calling the function fetchUsersPost. For some reason for one of the loops (I believe the second) it works properly, but for the first it does not.
The beginning of the fetch function is:
func fetchUsersPost(uid: String, postID: String) {
print("fetchUsersPost Posts/\(uid)/\(postID)")
Here is the output:
fetchUsersPost Posts/QUocyvGehdeaOO9vVnklwOrWH7l1/post:580077760
QUocyvGehdeaOO9vVnklwOrWH7l1 This is the uid!!!!!!
580077723 This is tfdsafdsafdsafdsafads4!!!!
fetchUsersPost Posts/ QUocyvGehdeaOO9vVnklwOrWH7l1/post:580077723
I wonder if the problem is related to the fact that on one of the print statements (the second) there is a space between the uid and the '/'
What is the problem?
You have 1 user with UID = QUocyvGehdeaOO9vVnklwOrWH7l1 who has 2 posts with 580077760 and 580077723 , if there is a space you need to verify that you create the child key correctly when you set the value for it
let keyValue = child.key
as UID:POSTID with no leading , trailing or middle spaces , also make sure whether these 2 posts have a valid value before you fetch them

Why is my Print statement not being executed?

I set up breakpoints all along my function, everything is running fine, but when it comes to the print part, it always skips over it.
func testForGettingAllValues(){
let uid = user2?.id
let ref = FIRDatabase.database().reference().child("user-reviews").child(uid!)
ref.observe(.childAdded, with: {(firstSnapshot) in
let reviewId = firstSnapshot.key
let messageReference = FIRDatabase.database().reference().child("reviews").child(reviewId)
messageReference.queryOrdered(byChild: "ratingNumber").observeSingleEvent(of: .value, with: {(reviewSnapshot) in
if reviewSnapshot.exists(){
if let values = reviewSnapshot.value as? [String:AnyObject]{
for reviews in values {
if let ratingNumber = reviews.value["ratingNumber"] as? String{
print("This is the ratingNumber list", ratingNumber)
}
}
}
}
})
})
}
}
In you nestedness, the following holds:
if binding to values is successful, it is of type [String:AnyObject]
each reviews instances is a named tuple of type (key: String, value: AnyObject)
when you try to bind to ratingNumber, you access the value property of reviews, and act as if this value property is a dictionary itself. However, it is not, it is of type AnyObject.
Most likely, in the third bullet above, lies the logical error, which in turn cause the innermost optional binding to fail. The compiler cannot know whether or not the AnyObject instance accessible by reviews.value is in fact wrapping a dictionary (an NSDictionary, perhaps?). You could attempt to first attempt a type conversion of it to the dictionary type you believe it to be, and thereafter attempting to access the "ratingNumber" key of the converted value.
Possibly (without the chance to test this out myself, due to your lack of a reproducable example ...):
if let ratingNumber = (reviews.value as? [String: String])?["ratingNumber"]

Ambiguous use of subscript?

I can make a Facebook SDK Graph Request to get a user's likes, but I'm having trouble taking the returned values and storing one of the keys in an array of Strings. The request returns an NSDictionary of keys/values. Then, using objectForKey I can get the data key which returns what I want: the id and name of the "liked" page on Facebook.
Data returns elements like this:
{
id = 486379781543416;
name = "Star Wars Movies";
},
I specifically want only the "name" of all of these objects and to throw them into an array [String]. I tried to loop through the objects but I'm getting error ambiguous use of subscript. Here's the relevant code:
request.startWithCompletionHandler{(connection:FBSDKGraphRequestConnection!, result:AnyObject!, error:NSError!) -> Void in
let resultdict = result as! NSDictionary
let likes = resultdict.objectForKey("data") as! NSArray
print("Found \(likes.count) likes")
print(likes)
for object in likes{
let name = object["name"] as! String //error: ambiguous use of subsript
print(name)
}
}
After doing some research it looks like the issue is with the NSArray and that I should instead use Swift data types. I tried casting it to a Swift array but I got different errors.
What's the best way to handle this error?
Thanks!
update: Here is what the facebook API request returns:
{
data = (
{
id = 111276025563005;
name = "Star Wars (film)";
},
{
id = 115061321839188;
name = "Return of the Jedi";
}
);
paging = {
cursors = {
after = MTE1MDYxMzIxODM5MTg4;
before = Mjc0NzYzODk2MTg4NjY5;
};
next = "https://graph.facebook.com/v2.5/10155262562690368/likes?access_token=<redacted>";
};
}
You should always use the native Swift collection types wherever possible as NSArray and NSDictionary are really type-inspecific, and therefore can easily trigger "ambiguous use of subscript" errors.
You'll also want to avoid force down-casting, in case you receive data that's in the wrong format, or no data at all. This situation would be more elegantly handled with a guard, in order to prevent a crash. If your program depends on the force down-casting succeeding, and therefore should crash – then you can always call fatalError in the guard, with a descriptive error message in order to assist you in debugging the problem.
If I understand your data structure correctly, the request returns an AnyObject that should be a [String:AnyObject] (A dictionary of strings to any objects). In the case of the "data" key, the AnyObject value is then a [[String:AnyObject]] (An array of dictionaries of strings to any objects).
Therefore you'll want to do your casting in two stages. First, using a conditional downcast on your result to cast it as a [String:AnyObject]. If this fails, then the else clause of the guard will be executed and the code will return. You'll then want to get out your "data" value (your 'likes' array), and conditionally downcast it to a [[String:AnyObject]]. Both of these statements will handle the possibility of resultDict or resultDict["data"] being nil.
guard let resultDict = result as? [String:AnyObject] else {return}
guard let likes = resultDict["data"] as? [[String:AnyObject]] else {return}
You can put whatever error handling logic you want in the brackets of these statements to handle cases in which the results dictionary doesn't exist, was the wrong format, or there wasn't a 'likes' array in it.
You can then get an array of 'like' names through using flatMap.
let likeNames = likes.flatMap{$0["name"] as? String}
This will create an array of the like names of each dictionary – if the like names don't exist or aren't strings, then they won't be added. Because the compiler knows for certain that likes is a [[String:AnyObject]] – there's no ambiguity in subscripting its elements.
If you want a more general approach such as you're doing in your question, you can use a guard statement within a for loop.
for object in likes {
guard let name = object["name"] as? String else {continue}
print(name)
}
Again, you can put whatever error handling you wish in the brackets of the guard.

how to don't get a nil value from NSUserDefaults in ViewDidLoad

I have a case where when my viewControler starts in viewDidLoad I have to load some data using NSUserDefaults.standardUserDefaults() which doesn't exist in this monent. This data are saved when I tap send Button in the same viewController and I need this data when I open this viewController again. Now it looks like that:
var orderHistory = [String:String]()
vievDidLoad(){
let userDefault = NSUserDefaults.standardUserDefaults()
let orderHistory = userDefault.objectForKey("orderHistory")
if orderHistory == nil {
self.orderHistory = orderHistory["name":"", "surname":""] as! [String:String]
} else {
self.orderHistory = orderHistory as! [String:String]
{
}// end viewDidLoad
In this moment I recieve an imformation, I have a problem with memory. How should I avoid this situation?
As Leo Dabus said you should try using the ?? nil coalescing operator.
ObjectForKey does not provide a default value because it doesnt know what kind of object it is until you set it the first time. This results in a nil crash if you try to access it value without having it set once.
Compare this to say "boolForKey" where you dont have to do this, because it knows you are dealing with boolean values and therefore defaults to false automatically.
You also dont have to create 2 orderHistory dictionaries, it just makes your code more confusing.
Try this instead
var orderHistory = [String:String]()
vievDidLoad(){
let userDefault = NSUserDefaults.standardUserDefaults()
orderHistory = userDefault.objectForKey("orderHistory") as? [String: String] ?? orderHistory
//than just use the 1 dictionary without the if statements or creating another one.
}// end viewDidLoad
You check if saved data exists (as? [String: String]) and update the dictionary accordingly. If no saved data exists it will use the default values in orderHistory (?? orderHistory), which in your case is an empty dictionary.
This way you dont have to do a nil check, its all done in that one line.
Also try putting your keys into structs or global files so that you avoid typos. I see people not doing this all the time and its really bad practice.
So for example, above your class create a struct
struct Key {
static let orderHistory = "OrderHistory"
}
and use it like so
...objectForKey(Key.orderHistory)
This code makes no sense:
if orderHistory == nil
{
self.orderHistory = orderHistory["name":"", "surname":""] as! [String:String]
}
The if statement guarantees that orderHistory is nil, thereby guaranteeing that the attempt to fetch keys from orderHistory will crash. Actually, that doesn't look like valid Swift. I would expect that line to throw a compiler error.
Are you trying to create a new dictionary?
If so, your code should read like this:
if orderHistory == nil
{
self.orderHistory = ["name":"", "surname":""]
}

How do I get Parse data as a String out of PFUser?

I am currently trying to get a value called "loot" out of the current user. I need the value as a String, but Swift is being stubborn and says it "cannot convert Anyobject to String". The Parse documentation for iOS says to use something like:
let score = gameScore["score"] as String
and so, I try this :
let lootAmount = user["loot"] as String
BTW 'user' is referring to the current user. When I try that, it gives error saying it's not convertible. I tried placing '!'s and '?'s wherever Xcode suggested, but it just crashed the app with no error.
So, how do I get the user value called "loot" as a String?
Loot is an NSNumber not an NSString or String.
You could convert it to a String like this:
if let loot = user["loot"] as? NSNumber {
let lootString = "\(loot)"
}
If you're not sure of an object's type, you can ask it using dynamicType:
print(user["loot"]!.dynamicType)
//prints `__NSCFNumber.Type`
You may need to downcast AnyObject. Try this: let lootAmount = user["loot"] as? String or unwrap your optional user if you haven't done so:
let currentUser = PFUser.currentUser()
if let user = currentUser {
let lootAmount = user["loot"] as String
}

Resources