update values from struct to Firestore in Swift - ios

I want to update values from struct,This is my struct code.
struct Usersdata {
let uid:String?
let facebook:String?
let google : String?
let name : String?
let age : Int?
let birthday : String?
let smokeage : Int?
let smokeaddiction : Int?
let smokebrand : String?
let gold : Int?
let score : Int?
let fish : Int?
let shit : Int?
let userimage : String?
init(aDoc: DocumentSnapshot) {
self.uid = aDoc.get("uid") as? String ?? ""
self.facebook = aDoc.get("facebook") as? String ?? ""
self.google = aDoc.get("google") as? String ?? ""
self.name = aDoc.get("name") as? String ?? ""
self.age = aDoc.get("age") as? Int ?? 0
self.birthday = aDoc.get("birthday") as? String ?? ""
self.smokeage = aDoc.get("smokeage") as? Int ?? 0
self.smokeaddiction = aDoc.get("smokeaddiction") as? Int ?? 0
self.smokebrand = aDoc.get("smokebrand") as? String ?? ""
self.gold = aDoc.get("gold") as? Int ?? 0
self.score = aDoc.get("score") as? Int ?? 0
self.fish = aDoc.get("fish") as? Int ?? 0
self.shit = aDoc.get("shit") as? Int ?? 0
self.userimage = aDoc.get("userimage") as? String ?? ""
}
}
I got values from my query func like this
func queryAUser() {
let docRef = self.db.collection("Users").document(userID).collection("userdata").document("userdata")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let aUser = Usersdata(aDoc: document)
self.local_userdata = aUser
} else {
print("Document does not exist")
}
}
}
I can use local_userdata to get my values , but I want to update new values to Firestore now .
Are there any solutions to update values from Usersdata.struct?

Let me give this a shot.
Assuming you have read in a user using the code in your question and the user is stored in self.local_userdata
Here's an example function to update a users name given a users uid and a new name
func updateUserName(withUid: String, toNewName: String) {
self.db.collection("users").document(withUid).setData( ["name": toNewName], merge: true)
}
To use this, read self.local_userdata.uid so we know which user we want to modify and pass in that uid and what the new name should be.
You could enhance this further to update any field for a certain user with this
func updateUserField(withUid: String, andField: String, toNewValue: String) {
self.db.collection("users").document(withUid).setData( [andField: toNewValue], merge: true)
}
of course if you are changing the fields you should also update the structure accordingly
self.local_userdata.name = "some name"

Related

Firebase print snapshot works fine and fetches the dictionary but when tried to access individual field data, returns nil for certain fields

I have a problem accessing values from certain fields in my firebase database. Right now this is how my structure looks in firebase:
messages:
messageId:
fromId:
text:
timestamp:
toId:
I am able to successfully upload the data to firebase when a user inputs a message to another user. And I am also able to successfully print the snapshot. But when I set the dictionary values and access it, only "fromId" and "toId" works but "timestamp" and "text" returns a nil value.
Pretty sure there is some sort of a wrong implementation in terms of taking the snapshot values and setting it. For your reference, I have included 3 files, one where the data model is defined, one where I upload data to firebase and one where I am trying to print it but I get nil.
The file where I am trying to print data but I get nil. Note: I am only getting nil when I am trying to print "text" and "timestamp" field values. "fromId" and "toId" works.
import UIKit
import Firebase
class MessagesController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
observeMessages()
}
var messages = [Message]()
func observeMessages(){
let ref = Database.database().reference().child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as?
Dictionary<String, AnyObject>{
let message = Message(dictionary: dictionary)
print(message.text)
print(message.fromId)
print(message.toId)
print(timestamp)
}
})
}
}
This is how I am uploading the data to firebase using a handle send function once the user has entered some text in the text box
#objc func handleSend(){
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.uid!
let fromId = Auth.auth().currentUser!.uid
let timestamp: Int = Int(NSDate().timeIntervalSince1970)
let values = ["fromId": fromId, "text":
inputTextField.text!, "timestamp": timestamp, "toId": toId] as
[String : Any]
childRef.updateChildValues(values)
}
Finally this is how I have declared my messages class:
class Message{
var fromId: String!
var text: String!
var timestamp: Int!
var toId: String!
init(dictionary: Dictionary<String, AnyObject>) {
if let text = dictionary["messageText"] as? String {
self.text = text
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let toId = dictionary["toId"] as? String {
self.toId = toId
}
if let timestamp = dictionary["creationDate"] as? Int {
self.timestamp = timestamp
}
}
}
When I print message.fromID, I get the data results in the console but when I print message.text or message.timestamp I get
nil
nil
nil
So in summary snapshot works, fromId, toID fields also work but for some reason the data from the text and timestamp fields are returned as nil
your are accessing values from dictionary with invalid key use text instead of messageText and use timeSamp instead of creationDate. like below
class Message{
var fromId: String!
var text: String!
var timestamp: Int!
var toId: String!
init(dictionary: Dictionary<String, AnyObject>) {
if let text = dictionary["text"] as? String {
self.text = text
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let toId = dictionary["toId"] as? String {
self.toId = toId
}
if let timestamp = dictionary["timestamp"] as? Int {
self.timestamp = timestamp
}
}
}

how to add information in an array with a class Swift

I've got a problem for adding some informations in an array.
My class Flights is define by the following :
class Flight{
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}}
In my main code I've got declare my array like this :
var datas: [Flight] = []
And finally I've this code to add some informations coming from firebase :
(I add some comment to show you what print() result)
if let user = Auth.auth().currentUser{
// user is connect
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let ev = ref.child("flights").child(userID!)
ev.observe(.childAdded, with: { (snapshot) -> Void in
let flightKey = snapshot.key
ref.child("flights").child(userID!).child(flightKey).observeSingleEvent(of: .value) {(snapshot) in
let value = snapshot.value as? NSDictionary
let date = value?["Date"] as? String ?? "no date"
let type = value?["aircraft-model"] as? String ?? "no type"
let registration = value?["aircraft-registration"] as? String ?? "no callsign"
let totalTime = value?["TOTAL-TIME"] as? String ?? "no total Time"
let deppartTime = value?["departure-time"] as? String ?? "no departure Time"
let deppartPlace = value?["departure-place"] as? String ?? "no departure Place"
let arrivalTime = value?["arrival-time"] as? String ?? "no arrival Time"
let arrivalPlace = value?["arrival-place"] as? String ?? "no arrival Place"
print("Date : \(date) - type : \(type) - registration : \(registration) - Etc ...")// Give me exactly the value I requested
self.datas.append(Flight(from: date, type, registration, totalTime, deppartTime, deppartPlace, arrivalTime, arrivalPlace))
print(self.datas)// Give me "MyProjectName.Flight ...
}
})
}else{
// si non connecté alors DECONNEXION !!!!
fatalError("error ...")
}
So I don't understand why if I print the received value from firebase it work but if I print the array value which is completed by the firebase received value it didn't work ?
Thanks for your help !
Flyer-74
Welcome :)
I think all is as expected and you're just seeing this because Swift doesn't know how to describe your objects.
To fix this, you should implement the CustomStringConvertible protocol in your Flight class (https://developer.apple.com/documentation/swift/customstringconvertible)
So something like
extension Flight: CustomStringConvertible {
var description: String {
var description = ""
description.append("date: \(date)\n")
description.append("type: \(type)\n")
description.append("regi: \(regi)\n")
//and so on
return description
}
}
Should give you what you are looking for.
Hope that helps you
You can try to adopt CustomStringConvertible protocol
class Flight : CustomStringConvertible {
var description: String {
return "\(date) \(type)" // add here any variable you want it to be printed
}
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}
}
You could add a custom debug description for your object by adding an extension to Flight, and make it conform to the CustomDebugStringConvertible protocol. Conformance to this protocol requires that you provide a property: var debugDescription: String { get }. Inside this string is where you have full control over the debug values for your custom Object.
extension Flight: CustomDebugStringConvertible {
var debugDescription: String {
return "Date: \(date), Type: \(type), Registartion: \(regi)"
}
}

swift 3.0 How can I access `AnyHashable` types in `Any` in Swift 3?

I'm using sqlite file to get the diaryEntriesTeacher from the authorId. it generates the following object of authorId when I print the variable authorId is nil
Code :-
func applySelectQuery() {
checkDataBaseFile()
objFMDB = FMDatabase(path: fullPathOfDB)
objFMDB.open()
objFMDB.beginTransaction()
do {
let results = try objFMDB.executeQuery("select * from diaryEntriesTeacher", values: nil)
while results.next() {
let totalCount = results.resultDictionary
let authorId = totalCount?["authorId"]!
print("authorId",authorId)
}
}
catch {
print(error.localizedDescription)
}
print(fullPathOfDB)
self.objFMDB.commit()
self.objFMDB.close()
}
output
This is how you access Dictionary of [AnyHashable : Any]
var dict : Dictionary = Dictionary<AnyHashable,Any>()
dict["name"] = "sandeep"
let myName : String = dict["name"] as? String ?? ""
In your case
let authorId = totalCount?["authorId"] as? String ?? ""
We need to convert the property we are trying to access to AnyHashable before using it.
In your case :
do {
let results = try objFMDB.executeQuery("select * from diaryEntriesTeacher", values: nil)
while results.next() {
let totalCount = results.resultDictionary
let authorId = totalCount?[AnyHashable("authorId")]!
print("authorId",authorId)
}
This is Swift. Use strong types and fast enumeration. Dictionary<AnyHashable,Any> is the generic type of a dictionary and can be cast to <String,Any> as all keys seem to be String.
do
if let results = try objFMDB.executeQuery("select * from diaryEntriesTeacher", values: nil) as? [[String:Any]]
for item in results {
let authorId = item["authorId"] as? String
let studentName = item["studentName"] as? String
print("authorId", authorId ?? "n/a")
print("studentName", studentName ?? "n/a")
}
}
....

How to work with Firebase without allowing optional values

I'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:
class Item{
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:
private func loadPosts(){
databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
var newPosts = [Post]()
for itemSnapShot in snapshot.children {
let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
newPosts.append(post!)
}
self.posts = newPosts
self.tableView.reloadData()
})
}
This guy is placed in my PostsViewController where I have my table view. This is my model:
class Post {
var ref: FIRDatabaseReference?
var title: String?
var answer: String?
var contentUrl: String?
var photoUrl: String?
var createdAt: String?
var feeling: String?
var kind: String?
var text: String?
var uid: String?
var measurements: Dictionary<String, String>?
//MARK: Initialization
init?(snapshot: FIRDataSnapshot){
ref = snapshot.ref
let data = snapshot.value as! Dictionary<String, Any>
title = data["title"]! as? String
answer = data["answer"] as? String
contentUrl = data["content_url"] as? String
photoUrl = data["photo_url"] as? String
createdAt = data["created_at"] as? String
feeling = data["feeling"] as? String
kind = data["kind"] as? String
text = data["text"] as? String
uid = data["uid"] as? String
measurements = data["measurements"] as? Dictionary<String, String>
}
}
I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.
So, does anybody know how can I use Firebase following Swift best practices?
Either you wish to allow the properties of your Post class to be nil or you don't.
If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.
If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.
class Post {
var ref: FIRDatabaseReference
var title: String
var answer: String
var contentUrl: String
var photoUrl: String
var createdAt: String
var feeling: String
var kind: String
var text: String
var uid: String
var measurements: [String : String]
//MARK: Initialization
init?(snapshot: FIRDataSnapshot) {
if let data = snapshot.value as? [String : Any] {
self.ref = snapshot.ref
title = data["title"] as? String ?? ""
answer = data["answer"] as? String ?? ""
contentUrl = data["content_url"] as? String ?? ""
photoUrl = data["photo_url"] as? String ?? ""
createdAt = data["created_at"] as? String ?? ""
feeling = data["feeling"] as? String ?? ""
kind = data["kind"] as? String ?? ""
text = data["text"] as? String ?? ""
uid = data["uid"] as? String ?? ""
measurements = data["measurements"] as? [String : String] ?? [:]
} else {
return nil
}
}
}
Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.
Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.
Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.
First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.
I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link
I recommend:
• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?
• make your post array public so that it can be accessed into the tableview.
Here's example:
class func getPosts(uid: String, _ completion: #escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
//update inside users node
var posts = [Post]()
Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
completion(nil, nil)
return
}
let n = postsDictionary.count
for postDictionary in postsDictionary {
let post = Post()
post.userID = uid
if let content = postDictionary.value["content"] as? String {
post.content = content
}
if let imageURL = postDictionary.value["imageURL"] as? String {
post.imageURL = imageURL
}
if let timeStamp = postDictionary.key as String! {
if let date = timeStamp.convertToDate() {
post.timeStamp = date
}
post.postIdentifier = timeStamp
}
posts.append(post)
if posts.count == n {
// Sort the array by the newest post
let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
completion(sortedPosts, nil)
}
}
}) { (error) in
completion(nil, error)
}
}
Assigning to tableview be like:
getPosts(uid: Current.user.userID!) { (posts, error) in
guard error == nil else {
print(error.debugDescription)
return
}
cell.label.text = posts[indexPath.item].content

how to make an attribute in the following call optional (ie allow nil)

A long time since I have written iOS code but I have the following Model in an iOS app and works great but now we are finding out that detail is optional and we should allow nil values. How would I adjust the initializer to support this? Sorry, I find the optionals a bit difficult to grasp (concept makes sense - executing it is difficult).
class Item{
var id:Int
var header:String
var detail:String
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String,
let detail = dictionary["detail"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = detail
}
and creating:
var items = [Item]()
if let item = Item(dictionary: dictionary) {
self.items.append(item)
}
As in above answer by #AMomchilov, you could assign the value only if it exists in your init method.
But also you could check for the value and then access it like below:
class Item {
var id:Int
var header:String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String //if there is value then it will assign else nil will be assigned.
}
}
let dictionary = ["id": 10, "header": "HeaderValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
And the console is :
10
'detail' is nil for this item
HeaderValue
And if there is `detail' value present then:
let dictionary = ["id": 10, "header": "HeaderValue", "detail":"DetailValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
Console:
10
DetailValue
HeaderValue
Remove detail from the guard (as now a nil value is acceptable), and assign self.detail to dictionary["detail"] as? String.
class Item {
var id: Int
var header: String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String
}
Edit: Improved based on Santosh's answer.

Resources