Swift 4 Unwrapping Dictionary from Firebase - swift

Here is the output of "print(dict)"...
["2018-10-17 11:19:51": {
firstname = Brooke;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Alvarez;
message = hshahyzhshbsbvash;
username = poiii;
}]
["2018-10-17 11:20:31": {
firstname = Trevor;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Bellai;
message = hey;
username = br9n;
}]
["2018-10-17 11:20:44": {
firstname = Amy;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Ikk;
message = hey;
username = nine9;
}]
My code...
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
// Store data in user.swift model
let firstnameData = dict[0]["firstname"] as! String
let idData = dict["id"] as! String
let lastnameData = dict["lastname"] as! String
let messageData = dict["message"] as! String
let usernameData = dict["username"] as! String
let rankData = dict["rank"] as! String
let propicrefData = dict["propicref"] as! String
let convoinfo = RecentConvo(firstnameString: firstnameData, idString: idData, lastnameString: lastnameData, messageString: messageData, usernameString: usernameData, rankString: rankData, propicrefString: propicrefData)
self.recentconvos.append(convoinfo)
print(self.recentconvos)
self.tableView.reloadData()
}
}
I'm trying to retrieve the dictionary within the first dictionary which is the value to the key which is the date associate with it. For example: 2018-10-17 11:19:51. However I cannot use this exact string to call it because I must do this without the knowledge of that string.
I tried this:
let firstnameData = dict[0]["firstname"] as! String
But it returns an error:
Cannot subscript a value of type '[String : Any]' with an index of type 'Int'

The error noted above is showing up because you were trying to access the element at a certain position (0) from the dictionary. Dictionaries are not ordered lists, and hence won't have a fixed order of elements to be accessed.
The logged dictionary doesn't really look like a dictionary. Assuming that it is a dictionary, and its keys are the date strings, you can use the following code snippet to parse the dictionary.
class RecentConversation {
var id: String?
var firstName: String?
var lastName: String?
var message: String?
var username: String?
var rank: String?
var propicref: String?
init?(dictionary: [String: Any]?) {
guard let dict = dictionary else {
// Return nil in case the dictionary passed on is nil
return nil
}
id = dict["id"] as? String
firstName = dict["firstname"] as? String
lastName = dict["lastname"] as? String
message = dict["message"] as? String
username = dict["username"] as? String
rank = dict["rank"] as? String
propicref = dict["propicref"] as? String
}
}
Usage:
let dateStrings = dict.keys.sorted {
// Sort in chronological order (based on the date string; if you need to sort based on the proper date,
// convert the date string to Date object and compare the same).
//
// Swap the line to $0 > $1 to sort the items reverse chronologically.
return $0 < $1
}
var conversations: [RecentConversation] = []
for date in dateStrings {
if let conversation = RecentConversation(dictionary: (dict[date] as? [String: Any])) {
conversations.append(conversation)
}
}

You were all very helpful, so I would like to start off by saying thank you. I went ahead and applied the method that lionserdar explained. (.allKeys)
// Fetch Recent Messages
func fetchRecentMsgs() {
// Direct to database child
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? NSDictionary {
print(dict)
print(dict.allKeys)
let keys = dict.allKeys
for key in keys {
print(key)
if let nestedDict = dict[key] as? [String: Any] {
print(nestedDict)
let firstnameData = nestedDict["firstname"] as! String
let idData = nestedDict["id"] as! String
let lastnameData = nestedDict["lastname"] as! String
let messageData = nestedDict["message"] as! String
let usernameData = nestedDict["username"] as! String
Worked for me so I hope this will help others too!

Related

How to know which initializer to use for reading data(Firebase)

I've got two initializers:
struct UserInfo{
let ref: DatabaseReference?
let key: String
let firstName: String
let lastName: String
let username: String
let pictureURL : String?
let admin : Bool
init(firstName: String, lastName:String,username:String,pictureURL:String?,admin:Bool, key:String = "" ){
self.ref = nil
self.key = key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = pictureURL
self.admin = admin
}
init?(snapshot:DataSnapshot){
guard let value = snapshot.value as? [String:AnyObject],
let firstName = value["firstName"] as? String,
let lastName = value["lastName"] as? String,
let username = value["userName"] as? String,
let profilePic = value["pictureURL"] as? String,
let admin = value["isAdmin"] as? Bool
else {return nil}
self.ref = snapshot.ref
self.key = snapshot.key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = profilePic
self.admin = admin
}
func toAnyObject()-> Any{
return [
"firstName": firstName,
"lastName": lastName,
"username": username,
"pictureURL":pictureURL as Any,
"isAdmin": admin
]
}
}
For reading most recent data I use this method combined with first init and it works:
let completed =
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
var newArray: [UserInfo] = []
if let dictionary = snapshot.value as? [String:Any]{
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let profilePic = dictionary["pictureURL"] as? String
let admin = dictionary["isAdmin"] as! Bool
let userInformation = UserInfo(firstName: firstName, lastName:
lastName, username: username,pictureURL: profilePic, admin: admin)
newArray.append(userInformation)
print(newArray)
completion(.success(newArray))
print(newArray)
}
Why and when do I need to use second init??
In Firebase tutorial on raywenderlich.com we gat example about: Synchronizing Data to the Table View using second init:
let completed = ref.observe(.value) { snapshot in
// 2
var newItems: [GroceryItem] = []
// 3
for child in snapshot.children {
// 4
if
let snapshot = child as? DataSnapshot,
let groceryItem = GroceryItem(snapshot: snapshot) {
newItems.append(groceryItem)
}
}
// 5
self.items = newItems
self.tableView.reloadData()
But my method works the same with first init.
The question is really asking about two things that functionally work the same.
In one case the snapshot is being "broken down" into its raw data (strings etc) within the firebase closure
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let userInformation = UserInfo(firstName: firstName, lastName: lastName...
and then passing that raw data to the struct. That object is then added to the array
In the second case the snapshot itself is passed to the struct
init?(snapshot:DataSnapshot) {
guard let value = snapshot.value as? [String:AnyObject],
and the snapshot is broken down into it's raw data within the object.
The both function the same.
It's a matter of readability and personal preference. Generally speaking having initializers etc within an object can make the code a bit more readable, the object more reusable and less code - see this pseudo code
DataObjects.infoRef.child(uid!).observe(.value){ snapshot, error in
let user = UserInfo(snapshot)
self.newArray.append(user)
})
That's pretty tight code.
Imagine if there were 10 places you wanted to access those objects within your app. In your first case, that code would have to be replicated 10 times - which could be a lot more troubleshooting. In my example above, the object itself does the heavy lifting so accessing them requires far less code.
Two other things. You may want to consider using .childSnapshot to access the data within a snapshot instead of a dictionary (either way works)
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
and please avoid force unwrapping optional vars
child(uid!)
as it will cause unstable code and random, unexplained crashes. This would be better
guard let uid = maybeUid else { return } //or handle the error

Added property to struct which in Swift - invalidates existing objects

I am new to Swift, but have some basic experience with Objective-C programming, and Swift seems much simpler.
However, I can't quite understand the struct thing. I followed a tutorial on how to use Firebase Realtime Database, and this tutorial were using a model to store the data.
But when I modified the struct with additional properties, the previously saved entries in the database is not showing up. I think it's because the model doesn't recognize the object in the database because it has different properties, but how can I make a property optional? So that old entries in the database with different structure (missing properties) are still valid and showing up?
Here is the model. The new property added is all the references to the description.
import Foundation
import Firebase
struct InsuranceItem {
let ref: DatabaseReference?
let key: String
let name: String
let timestamp: Int
let itemValue: Int
let description: String?
let userId: String?
init(name: String, timestamp: Int, itemValue: Int = 0, description: String = "", userId: String, key: String = "") {
self.ref = nil
self.key = key
self.name = name
self.timestamp = Int(Date().timeIntervalSince1970)
self.itemValue = itemValue
self.description = description
self.userId = userId
}
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: AnyObject],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int,
let description = value["description"] as? String,
let userId = value["userId"] as? String else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
self.description = description
self.userId = userId
}
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description!,
"userId": userId!
]
}
}
The problematic bit is your failable init, init?(snapshot: DataSnapshot). You fail the init even if an Optional property is missing, which is incorrect. You should only include the non-Optional properties in your guard statement, all others should simply be assigned with the optional casted value.
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: Any],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
// Optional properties
let description = value["description"] as? String
let userId = value["userId"] as? String
self.description = description
self.userId = userId
}
Unrelated to your question, but your toAnyObject function is unsafe, since you are force-unwrapping Optional values. Simply keep them as Optionals without any unwrapping and add as Any to silence the warning for implicit coersion.
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description as Any,
"userId": userId as Any
]
}

How to unwrap an callback in swift?

I'm using FB Login for my app in Swift and when I make a graph request, it returns the following result:
Optional({
email = "arjun.ramjams#gmail.com";
id = 10218497873670001;
name = "Arjun Ram";
})
Now, how should I read, each individual value(i.e: email, id and name)?
func fetchProfile() {
let parameters = ["fields": "id,email, first_name, last_name"]
GraphRequest(graphPath: "me",parameters: parameters).start{(connection, user, Err) in
if Err != nil {
print(Err!)
return
}
let dic = user as! NSDictionary
let userID = dic["id"] as! String
let Email = dic["email"] as! String
let fname = dic["first_name"] as! String
let lname = dic["last_name"] as! String
}
}
The result is a Dictionary of type [String:Any]?.
let result:[String:Any]? = [
"email":"arjun.ramjams#gmail.com",
"id" : 10218497873670001,
"name":"Arjun Ram"
]
You can fetch the fields from result like,
let email = result?["email"] as? String
let id = result?["id"] as? Int
let name = result?["name"] as? String
You need to use if let or guard let for unwraping an object or variable
e.g.:
if let result = result as? [String: Any] {
print(result[“email”])
}
or
guard let result = result as? [String: Any] else {
// return something
}

Swift code problem - trying to cast objects out from a NSMutableArray as a custom object

Here is my code and it fails to execute as noted below.
I am trying to cast an object to my custom data type called UserData.
First problem I have is don't understand how to get the value out of the array correctly
Second I cannot seem to cast the object as the type I need, UserData. What I am doing wrong?
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
var users = NSMutableArray();
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
for i in 0 ..< jsonResult.count {
print("loop count :", i );
jsonElement = jsonResult[i] as! NSDictionary
let user = UserData()
//the following insures none of the JsonElement values are nil through optional binding
if let UserID = jsonElement["Userid"] as? String,
let firstName = jsonElement["First_Name"] as? String,
let lastName = jsonElement["Last_Name"] as? String,
let userSessionID = jsonElement["Session_ID"] as? String
{
user.UserID = UserID
user.FirstName = firstName
user.LastName = lastName
user.UserSessionID = userSessionID
print("users firstName:", user.FirstName ?? "blank");
}
users.add(user)
}
print("users size:", users.count); // this shows 2
// So i know I have data loaded... BUT when i try and
// get it then it all goes to heck. See below
// NOT SURE what I am doing here...
// Thought it was java like where I could just get a
// item from the NSMutableArray using an index value
// then cast it as my UserData object
// and print the output... but this does not work
// Why is this so hard??
let userDataVal = users.index(of: 0) as! UserData;
print("firstName:", userDataVal.FirstName);
}
Below is how I would write this function. Some comments
No NS... classes used, instead I use native arrays and dictionaries
If JSONSerialization.jsonObject generates an error the function is exited
Local variables are define as close as possible to as where they are used
Create an init method for your UserData struct/class that takes the values as parameters so you can do let user = UserData(userId: UserId, firstName:... instead.
Name local variables and properties with a first lowercase character, it makes it easier to read the code
func parseJSON(_ data:Data) {
var jsonResult: [[String: Any]]?
do {
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as? [[String: Any]]
} catch let error as NSError {
print(error)
return
}
guard let result = jsonResult else {
return
}
var users = [UserData]()
for jsonElement in result {
if let UserID = jsonElement["Userid"] as? String,
let firstName = jsonElement["First_Name"] as? String,
let lastName = jsonElement["Last_Name"] as? String,
let userSessionID = jsonElement["Session_ID"] as? String
{
var user = UserData()
user.UserID = UserID
user.FirstName = firstName
user.LastName = lastName
user.UserSessionID = userSessionID
users.append(user)
}
}
for user in users {
print("firstName:", user.FirstName);
}
}
Better approach would be to use JSONDecoder() instead of a JSONSerailizer. Try using the following code.
struct User: Codable {
var userId, firstName, lastName, userSessionId: String
enum CodingKeys: String, CodingKey {
case userId = "Userid"
case firstName = "First_Name"
case lastName = "Last_Name"
case userSessionId = "Session_ID"
}
}
func parseJSON(_ data: Data) {
do {
let users = try JSONDecoder().decode([User].self, from: data)
users.forEach { user in
print("users first name:", user.firstName)
}
} catch {
print(error.localizedDescription)
}
}

Firebase Data with Eureka & Init

I'm confused on how to properly save data to firebase with Eureka, using init. Resources I have checked out don't seem to clarify my answer. Not sure the best way to do this.
I have a file called User.swift that contains a struct:
`struct UsersInfo {
let email: String
let password: String
let first: String
let last: String
let about: String
let age: Int
let city: String
let firebaseReference: DatabaseReference?
init(email: String, password: String, first: String, last: String, about: String, age: Int, city: String) {
self.email = email
self.password = password
self.first = first
self.last = last
self.about = about
self.age = age
self.city = city
self.firebaseReference = nil
}
init(snapshot: DataSnapshot) {
let snapshotValue = snapshot.value as! [String : AnyObject]
self.email = snapshotValue["email"] as! String
self.password = snapshotValue["password"] as! String
self.first = snapshotValue["first"] as! String
self.last = snapshotValue["last"] as! String
self.age = snapshotValue["age"] as! Int
self.city = snapshotValue["city"] as! String
self.about = snapshotValue["about"] as! String
self.firebaseReference = snapshot.ref
}
} // End Struct.
Then I have a file/view controller that imports Eureka using FormViewController. My Eureka rows that are set to values, are located in this file as followed:
// Get the value of a single row
let emailrow: EmailRow? = form.rowBy(tag: "emailRowTag")
let emailvalue = emailrow?.value
// Get the value of a single row
let passwordrow: PasswordRow? = form.rowBy(tag: "passwordRowTag")
let passwordvalue = passwordrow?.value
// Get the value of a single row
let firstrow: NameRow? = form.rowBy(tag: "firstRowTag")
let firstvalue = firstrow?.value
// Get the value of a single row
let lastrow: TextRow? = form.rowBy(tag: "lastRowTag")
let lastvalue = lastrow?.value
// Get the value of a single row
let aboutrow: TextRow? = form.rowBy(tag: "aboutRowTag")
let aboutvalue = aboutrow?.value
// Get the value of a single row
let agerow: IntRow? = form.rowBy(tag: "ageRowTag")
let agevalue = agerow?.value
// Get the value of a single row
let cityrow: PushRow<String>? = form.rowBy(tag: "cityRowTag")
let cityvalue = cityrow?.value
In this file/view controller, I have a custom button using ButtonRow from Eureka. I implemented a function to do this and called it onCellSelection. The values of rows is what confusing me with Firebase and what I need to put in my snapshotValue dictionary in the User.swift file and to execute in FormViewController. Thanks!
The first thing I did was give the row of type TextRow a tag
so I had something like
row.tag = "username"
Then after that I was able to retrieve that tag with the following code
let usernameRow: TextRow? = form.rowBy(tag: "username")
let username: String? = usernameRow?.cell.textField.text
Firebase only allows strings to push to the database so now that you have that, you can call the setValue function and pass the variable username
firebaseref.setValue("username": username)