Create User in firebase - Realtime Database - swift

I stuck with creating a Firebase User.
After creating my database looks like this
I created a struct User but I don't know how to create the correct dictionary
struct User {
let email : String
let name : String
let lastname : String
let phone : String
let password : String
let uid: String
let car : [Car]
init(uid: String, dictionary: [String: Any] ) {
self.uid = uid
self.email = dictionary["email"] as? String ?? ""
self.name = dictionary["name"] as? String ?? ""
self.lastname = dictionary["lastname"] as? String ?? ""
self.phone = dictionary["phone"] as? String ?? ""
self.password = dictionary["password"] as? String ?? ""
self.car = dictionary["car"] as? String ?? "" <- this is wrong
}
struct Car {
let firstCar : [FirstCar]}
struct FirstCar {
let brandName : String
let capacity : String
let carName : String
let fueal : String
let model : String
let power : String
let year : String}
help

I think you're asking about how to populate the User object from the Realtime Database when there's a child node involved that is also a key: value pair.
My suggestion is to keep Firebase 'firebasey' as long as possible by using DataSnapshots and not cast to dictionaries; it makes getting data from child nodes much easier and cleaner.
Here's and example of reading a users node from Firebase
var userArray = [UserClass]()
func readAllUsers() {
let usersRef = self.ref.child("users") //self.ref points to my firebase
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let user = UserClass(userSnap: child)
self.userArray.append(user)
}
})
}
and then the UserClass with a child key: value pair with a car brand as a child
class UserClass {
var key = ""
var userName = ""
var userEmail = ""
var type = ""
var brand = ""
convenience init(userSnap: DataSnapshot) {
self.init()
self.key = userSnap.key
self.userName = userSnap.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.userEmail = userSnap.childSnapshot(forPath: "email").value as? String ?? "No email"
let carSnap = userSnap.childSnapshot(forPath: "car") //retuns the car node
let firstCarSnap = carSnap.childSnapshot(forPath: "firstCar") //return the firstCar node
self.brand = firstCarSnap.childSnapshot(forPath: "brandName").value as? String ?? "No Brand"
}
}
It's a good idea to store users by their uid and also keep track of that in the object in case you need to reference that user or update their node elsewhere in your code.
Note this line in the above code
for child in snapshot.children.allObjects as! [DataSnapshot]
preserves the ordering of the users when they are read in.

Related

Must be a non-empty string and not contain '.' '#' '$' '[' or ']''

Please help! I am experiencing an app crash.
public enum memberships {
case noMembership
case paid
case freeTrial
case trialExpired
}
public class DataManager {
private static let uuid = UIDevice.current.identifierForVendor!.uuidString
private static let user = Auth.auth().currentUser
private static let userRef = Database.database().reference().child("Users").child(user?.uid ?? "")
static var currentStatus: memberships? = nil
/**
Get's the current user's info from Firebase
and returns the info as a User object
*/
class func getUser(completion: #escaping (User?) -> ()) {
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
// UIPasteboard.general.string = uuid
let uuid = snapshot.key
let name = value["name"] as? String ?? ""
let email = value["email"] as? String ?? ""
let dateJoined = value["dateJoined"] as? String ?? ""
let membershipString = value["membership"] as? String
let alertsStartTime = value["alertsStartTime"] as? String ?? ""
let alertsEndTime = value["alertsEndTime"] as? String ?? ""
let alertsFrequency = value["alertsFrequency"] as? Int ?? 1
let alertsType = value["alertsType"] as? String ?? ""
let isEnable = value["isEnable"] as? Bool ?? true
//Gets users current membership
var membershipStatus: memberships!
if membershipString == "Paid" {
membershipStatus = .paid
}else if membershipString == "NoMembership" {
membershipStatus = .noMembership
}else{
membershipStatus = Utils.getUserMembershipStatus(dateJoined: dateJoined)
}
let user = User(uuid: uuid, name: name, email: email, dateJoined: dateJoined, membership: membershipStatus, alertsStartTime: alertsStartTime, alertsEndTime: alertsEndTime, alertsType: alertsType, alertsFrequency: alertsFrequency, isEnable: isEnable)
completion(user)
}else{
completion(nil)
}
}) { (error) in
print(error.localizedDescription)
completion(nil)
}
}
Your user object is empty. So user?.uid is nil. Which means child(user?.uid ?? "") -> child("").
Firebase does not accept empty strings as key values (It also does not accepts strings which includes '.' '#' '$' '[' or ']'' as keys).
So in your case make sure user is logged or use different key value.

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

Swift 4 Unwrapping Dictionary from Firebase

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!

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)

Download address from firebase in mapView

Trying to upload an address from firebase and post it on mapView. But for some reason the address doesn't want to unload. The address spelled out by the string in firebase example - Moscow, Street so-and-so, house 1. What could be the reason for not loading the data?
var allAddresses: String = ""
addressRef = Database.database().reference(withPath: "Address")
addressRef.observe(.value, with: { (snapshot) in
let value = snapshot.value as! NSDictionary
self.allAddresses = value["address"] as? String ?? ""
})
}
Firebase:
{
«Address» : {
«AddressOne» : {
"address" : "Москва, Пресненская набережная д.8, квартира 195, подъезд 94",
},
"AddressTwo» : {
"address" : "Москва, ул. Правды д.24 строение 3",
},
"AddressThree» : {
"address" : "Москва,ул.Электрозаводская д.21",
}
}
}
You're attaching a value observer to /Address, which means that you get a snapshot with all data at that location. Since there are multiple child addresses, your code will need to handle those.
The simplest way to do that is by listening for .childAdded instead of .value:
var allAddresses: String = ""
addressRef = Database.database().reference(withPath: "Address")
addressRef.observe(.childAdded, with: { (snapshot) in
let value = snapshot.value as! NSDictionary
self.allAddresses = value["address"] as? String ?? ""
})
Now your code gets triggered with each individual address.
You can also stick to observing .value and then loop over the results in the snapshot:
var allAddresses: String = ""
addressRef = Database.database().reference(withPath: "Address")
addressRef.observe(.value, with: { (snapshot) in
for address in snapshot.children.allObjects as [FIRDataSnapshot] {
let value = address.value as! NSDictionary
self.allAddresses = value["address"] as? String ?? ""
})
})
Use optional unwrapping and set breakpoints on some lines to see where your code doesn't do what you want :
var allAddresses: String = ""
addressRef.observe(.value, with: { (snapshot) in
if let value = snapshot.value as? [String:Any] {
if let address = value["address"] as? String {
self.allAddresses = address
} else {
print("no address in value")
}
} else {
print("no value from firebase")
}
})
}
You should also use swift types in swift, it's a good practice.