Until recently, I have no had a problem to be able to pull my data from the database - and only when I decided to add another variable (totalDistance) did it start to break, however when attempting to remove this newly written code, the error stayed.
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
this is the error I am getting when I attempt to go onto the Profile page which is causing the error, here is the function I have wrote to be able to write the labels -
let ref = FIRDatabase.database().reference()
let UserID = FIRAuth.auth()?.currentUser?.uid
ref.child("user").child(UserID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let displayname = value?["nickname"] as? String ?? "Nickname Missing"
let bronzeMedals = value?["bronzeMedals"] as? String ?? "no bronze in db"
let silverMedals = value?["silverMedals"] as? String ?? "no silver in db"
let goldMedals = value?["goldMedals"] as? String ?? "no gold in db"
let totalDistance = value?["totalDistance"] as? String ?? "no total in db"
print(bronzeMedals, silverMedals, goldMedals)
self.displaynameLabel.text = ("Username: \(displayname)")
self.goldmedalsLabel.text = ("Gold medals: \(goldMedals)")
self.silvermedalsLabel.text = ("Silver medals: \(silverMedals)")
self.bronzemedalsLabel.text = ("Bronze medals: \(bronzeMedals)")
self.totaldistanceLabel.text = ("Total distance: \(totalDistance)")
})
This method of calling the informaiton has worked flawlessy until I had attempted to add another variable, I am very confused by this error and cannot seem to find out how to fix it - no matter how many different errors I look up which have already been answered, so any information will be very essential and well appreciated.
Here is the database file of the user - which is being called,
age:
"18"
bronzeMedals:
"2"
goldMedals:
"3"
nickname:
"Test Account"
silverMedals:
"5"
totalDistance:
"2"
You really need to change you code so that you are not working with optionals.
let ref = FIRDatabase.database().reference()
guard let user = FIRAuth.auth()?.currentUser else { return }
ref.child("user").child(user.uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value as? Dictionary<String,String> else { return }
let displayname = value["nickname"] ?? "Nickname Missing"
let bronzeMedals = value["bronzeMedals"] ?? "no bronze in db"
let silverMedals = value["silverMedals"] ?? "no silver in db"
let goldMedals = value["goldMedals"] ?? "no gold in db"
let totalDistance = value["totalDistance"] ?? "no total in db"
// Update Labels
self.displaynameLabel.text = ("Username: \(displayname)")
self.goldmedalsLabel.text = ("Gold medals: \(goldMedals)")
self.silvermedalsLabel.text = ("Silver medals: \(silverMedals)")
self.bronzemedalsLabel.text = ("Bronze medals: \(bronzeMedals)")
self.totaldistanceLabel.text = ("Total distance: \(totalDistance)")
})
Related
I'm retrieving a set of documents of events that contains Strings, Dates and a Timestamp. Strings are no problem but recently I added Dates and Timestamp to my model they always get returned as nil, which I have set as default, even though I can clearly see in the Firebase console they are stored correctly.
init setup for DocumentSnapshot retrieval
init?(snapshot:DocumentSnapshot){
self.eventId = snapshot.get("eventId") as? String ?? "No event Id"
self.byId = snapshot.get("byId") as? String ?? "No uid"
self.adminUser = snapshot.get("adminUser") as? String ?? "No admin user"
//self.eventCreated = snapshot.get("eventCreated") as? Timestamp ?? "No Event Created Date"
self.eventName = snapshot.get("eventName") as? String ?? "No Event Name"
self.eventLocation = snapshot.get("eventLocation") as? String ?? "No Location"
self.eventStart = snapshot.get("eventStart") as? Date ?? nil
self.eventEnd = snapshot.get("eventEnd") as? Date ?? nil
}
Results from DocumentSnapshot
adminUser: "",
byId: "juYtTP509rhXYPd433",
eventCreated: nil, //Timestamp retrieved as nil
eventId: "gC2RVdUuB9CD66JEYM18",
eventName: "test",
eventLocation: "",
eventStart: nil, //Date retrieved as nil
eventEnd: nil, //Date retrieved as nil
Events Model
struct Event {
var adminUser = ""
var byId = ""
var eventCreated:Timestamp?
var eventId = ""
var eventName = ""
var eventLocation = ""
var eventStart:Date? = nil
var eventEnd:Date? = Date(timeIntervalSince1970: 0)
Please let me know if I need to add the method here for better context?
Firestore doesn't have an internal Date type. Internally, it will create a Timestamp when it receives either Dates or Timestamp as input, so that's what your code should expect coming out of DocumentSnapshot in either case (never a Date).
I am trying to retrieve a child of a child. the entire snapshotValue returns null. When I retrieve the same data as a child (not nested) it retrieves fine.
I'm Using XCode 10 and Swift 4
To troubleshooting purposes, I have two nodes called 'Promoters'. One at the root and one nested inside a 'Partners' child (preferred). I will remove the top level node when I get the nested node working.
Here is the data structure:
"Partners" : {
"Acts" : [hidden],
"Promoters" : [ null, {
"Cell" : hidden,
"Contact Name" : “hidden”,
"Email" : “hidden”,
"Facebook" : “hidden“,
"Title" : "CHORD Productions"
} ]
},
"Promoters" : {
"chord" : {
"Title" : "Chord Productions"
}
}
This retrieves the data I'm looking for (a list of Titles to populate a picker):
let promotersDB = Database.database().reference().child("Promoters")
promotersDB.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String, String>
let promoterName = snapshotValue["Title"]!
let promoter = PromoterClass()
promoter.promoterName = promoterName
self.promoterArray.append(promoter)
let isSuccess = true
completion(isSuccess)
}
This returns nil:
let promotersDB = Database.database().reference().child("Partners").child("Promoters")
promotersDB.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String, String>
let promoterName = snapshotValue["Title"]!
let promoter = PromoterClass()
promoter.promoterName = promoterName
self.promoterArray.append(promoter)
let isSuccess = true
completion(isSuccess)
}
I'd prefer observeSingleEvent, but this also returns nil:
let promotersDB = Database.database().reference().child("Promoters")
promotersDB.observeSingleEvent(of: .value, with: { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String, String>
let promoterName = snapshotValue["Title"]!
let promoter = PromoterClass()
promoter.promoterName = promoterName
self.promoterArray.append(promoter)
let isSuccess = true
completion(isSuccess)
})
The error is:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
**I am using force unwrapping for now to be reviewed at a later date after investigating how much of the data integrity can be done with backend rules :)
Any assistance would be greatly appreciated.
I think firebase usually suggests the Fan Out method when dealing with data trees, so you shouldn't nest array like "Promoters" anyways.
You'll also want to identify each of the Promoters by a uid instead of by their name (incase you have to change their name in the system).
So if you want, try to restructure your data like this:
let uid = Database.database().reference().child("Promoters").childByAutoId()
// uid will be some super long string of random letters and numbers, for example like: 1hjK2SCRV2fhCI0Vv3plGMct3mL2
"Promoters" : {
"1hjK2SCRV2fhCI0Vv3plGMct3mL2" : {
"Title" : "Chord Productions"
}
}
And then when you want to query values within that promoter's branch:
let titleRef = Database.database().reference().child("Promoters").child(uid)
titleRef.observeSingleEvent(of: .value) { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
let promoter = PromoterClass()
promoterName = dict["Title"] as? String
promoter.promoterName = promoterName
}
}
This code in Swift Xcode 10.1 keeps throwing a Thread 1: signal SIGABART error and I don't know why.
let item = UserDefaults.standard.object(forKey: "To Does")
var arrayItem:[String]
print("about to check")
if let arrayItem2 = item as! [String]?{
arrayItem = arrayItem2
arrayItem.append(textInput.text!)
UserDefaults.standard.set(arrayItem, forKey: "To Does")
print(arrayItem)
}else{
UserDefaults.standard.set(textInput.text!, forKey: "To Does")
}
textInput.text = ""
Never use a forced cast with if let. Never force cast to an optional. Neither make any sense.
Even once you fix that you have a problem. You first write a string to user defaults if there is nothing yet for the key. But then you try to read an array. That will never work.
Simplify your code and always write an read an array.
var items = UserDefaults.standard.array(forKey: "To Does") as? [String] ?? []
items.append(textInput.text!)
UserDefaults.standard.set(items, forKey: "To Does")
Try printing an item to console. Chances are it's neither nil, nor an [String] and it fails a forced cast.
If it's not - you can use a simpler cast. Try this in playground:
let item: Any? = 2
var arrayItem: [String] = []
print("about to check")
if let arrayItem2 = item as? [String] {
arrayItem = arrayItem2
arrayItem.append("foo")
print(arrayItem)
} else {
print("doing nothing")
}
Outputs:
about to check
doing nothing
This is a quick output of my Firebase tree, id like to access people_on_this_planit / userID node. My only issue is that node is within an auto ID; please any help would be appreciated. Thanks
planits
-LEmgxuG_13KNA5inRaB
Planit Title: "Some title"
people_on_planit
-LEmh6IxqguVBJEwghZv (auto ID)
userID: "kkdiEW0D9"
senderId: "39FdLfdIO8832"
Right now the code i am using is as follows, but i am getting nil when i print peopleonplanit.
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let planitsRef = ref.child("planits")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let dict = childSnap.value as! NSDictionary
let planKey = childSnap.key
//print(planKey)
print(dict)
let title = dict["Planit Title"] as! String
let senderID = dict["senderId"] as! String
let peopleOnPlanit = dict["people_on_planit"] as? String
print(peopleOnPlanit)
}
Here's the code to read the Firebase structure proposed in the question
let planitsRef = self.ref.child("planits")
planitsRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let planSnap = child as! DataSnapshot
let planDict = planSnap.value as! [String: Any]
let title = planDict["plan_title"] as! String
let senderId = planDict["sender_id"] as! String
print("plan title: \(title) senderId: \(senderId)")
let peopleOnPlanitSnap = planSnap.childSnapshot(forPath: "people_on_planit")
for peopleChild in peopleOnPlanitSnap.children {
let peopleChildSnap = peopleChild as! DataSnapshot
let userSnapDict = peopleChildSnap.value as! [String: Any]
let userId = userSnapDict["user_id"] as! String
print(" userId: \(userId)")
}
}
})
and the output
plan title: Some title senderId: 39FdLfdIO8832
userId: uid_0
userId: uid_1
-- A couple of notes --
Since the associated users appear to be stored by their user Id and that's the only data stored within that node, you may want to change this node
people_on_planit
-LEmh6IxqguVBJEwghZv (auto ID)
userID: "kkdiEW0D9"
to look like this
people_on_planit
uid_x: true //uid_x would be kkdiEW0D9 etc
uid_y: true
it's a bit shallower and cleaner and requires less code to read it. Also, you can query it more easily if that ever comes up.
also note that I changed the naming convention slightly; you are welcome to format keys however you want but I went with keys like plan_title instead of PlanIt Title and sender_id and user_id instead of senderId and userId. My practice is for actual firebase keys I use all lowercase, underscore instead of spaces, and in code, I use lower/uppercase with no spaces. So if you copy paste my code, you'll need to change those.
Can anyone help me figure out why this code is behaving thusly...
When opening the app for the first time a "User" is created (if it doesn't already exist, which it doesn't the first time) and then the user is saved along with his/her golf "clubs". I get confirmation of the user saved and the clubs saved in the console. HOWEVER, when I close the app and reopen it the user is fetched but the clubs are not. What am I missing here? Let me know if you need/want to see any screen captures beyond this code...
//MARK: Core Data Variables
var user : User!
var userClubs = NSMutableSet()
var currentRound : Round!
var managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
func prepareUser() {
let fetchRequest = NSFetchRequest(entityName: "User")
let sortDescriptor = NSSortDescriptor(key: "createdTime", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let fetchResults = self.managedObjectContext.executeFetchRequest(fetchRequest, error: nil) as? [User] {
if fetchResults.count > 0 {
self.user = fetchResults[0]
println("First user: \(self.user!.firstName) \(self.user!.lastName)")
let fetchRequestClubs = NSFetchRequest(entityName: "Club")
if let fetchResults2 = self.managedObjectContext.executeFetchRequest(fetchRequestClubs, error: nil) as? [Club] {
if fetchResults2.count > 0 {
println("test: \(fetchResults2[0].type)")
}
}
} else {
println("No user yet")
var newUser : User = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: self.managedObjectContext) as! User
newUser.createdTime = NSDate()
managedObjectContext.save(nil)
var i = 0
println("before array: clubsArray is \(clubsArray.count) clubs long")
var clubs = NSMutableSet.mutableSetValueForKey("clubs")
for newClub in clubsArray {
var club : Club = NSEntityDescription.insertNewObjectForEntityForName("Club", inManagedObjectContext: self.managedObjectContext) as! Club
club.type = clubsArray[i].type as String
club.estimatedMinYardage = clubsArray[i].minDistance as Int
club.estimatedMaxYardage = clubsArray[i].maxDistance as Int
club.lowerBound = clubsArray[i].lowerBound as Int
club.upperBound = clubsArray[i].upperBound as Int
//userClubs.addObject(club)
managedObjectContext.save(nil)
//club.setValue(newUser, forKey: "user")
println("\(club.type)")
i++
}
//user.setValue(userClubs, forKey: "clubs")
prepareUser()
}
}
}
Here's the console output from the first run:
No user yet
before array: clubsArray is 17 clubs long
Putter
LW
SW
PW
9i
8i
7i
6i
5i
5W
... [the rest of the clubs]
First user: Your Name
test: 7i
And from the second run after closing and reopening the app:
First user: Your Name
Bloody hell. I was trying to set 1 of the 17 instances of the Club class to a value that was less than it's minimum value allowed in the data model. If anyone else is having a similar issue check your min/max and default in addition to what #Tom-Harrington suggested in the comments of the original question. Dunno if Apple could get some kind of warning for this kind of thing into future versions of XCode.