Can't extract Dates or Timestamps from Firestore DocumentSnapshot - swift

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).

Related

Error in Realm invalid property name when I want to filter data, property not found

I get the error *** Terminating app due to uncaught exception 'Invalid property name', reason: 'Property 'read' not found in object of type 'Book'' when I want to filter my data. My structure:
class Book: Object, Codable {
#objc dynamic var author = ""
#objc dynamic var title = ""
#objc dynamic var imageLink = ""
#objc dynamic var category = "Lk"
#objc dynamic var date = Date()
convenience init(withBookDict: [String: Any]) {
self.init()
self.author = withBookDict["author"] as? String ?? "No Author"
self.title = withBookDict["title"] as? String ?? "No Title"
self.imageLink = withBookDict["imageLink"] as? String ?? "No link"
self.category = withBookDict["category"] as? String ?? "No category"
}
}
my code for filtering data is this:
let filteredread = realm.objects(Book.self).filter({ $0.category == "read"})
but I also tried this:
let filteredread = realm.objects(Book.self).filter("category == 'read'")
also I did update my realm pod since there have been version issues.
There's actually nothing wrong with your code, other than a "typo" - but there's other stuff to consider.
Fixing the typo
let filteredread = realm.objects(Book.self).filter({ $0.category == "read"})
should be
let filteredread = realm.objects(Book.self).filter { $0.category == "read"}
note removing the () from the filter function (which is actually a Swift .filter). This is more for clarity than anything as using swift functions on Realm objects can cause issues.
That query will first read all of the Book objects, then perform a Swift filter on the results and return LazyFilterSequence<Results> object. What that means is the results are disconnected from Realm and will not auto-update, which is one of the key features of realm.
If you want to have the Results update, do not use Swift functions; use Realm functions only
let filteredread = realm.objects(Book.self).filter("category == 'read'")
which returns a Results, which will auto-update as the underlying data changes.
Note: that function matches what's in your question and is perfectly legitimate code and would not throw any errors.
The way to filter Realm results according to a string property is :
let filteredResult = realm.objects(Model.self).filter("propertyName == %#", "value")
In your case it would be:
let filteredread = realm.objects(Book.self).filter("category == %#", "read")

SwiftUI, Firestore get array of object within object

I am working on a recipe-app connected to firestore and have trouble reading the data saved in the database. I save a recipe that consists of title, id etc but it also contains an array of ingredients. This array is a struct containing id, name and amount. I am able to get the recipe object but the array of ingredients is empty. This is how is get the recipe
private func listenForRecipes() {
db.collection("recipe").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.recipes = documents.map { queryDocumentSnapshot -> RecipePost in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let steps = data["steps"] as? [Step] ?? []
let ingredients = data["ingredients"] as? [Ingredient] ?? []
let serves = data["serves"] as? Int ?? 0
let author = data["author"] as? String ?? ""
let authorId = data["authorId"] as? String ?? ""
let category = data["category"] as? String ?? ""
let image = data["image"] as? String ?? ""
print("\(ingredients)")
return RecipePost(title: title, steps: steps, ingredients: ingredients, serves: serves, author: author, authorId: authorId, category: category, image: image)
}
}
}
Thankful for any help.
The data that you're getting from Firebase is coming back to you in the form of a [String:Any] dictionary. Your current code is taking those dictionary keys (title, author, etc) and doing optional casts (the as?), telling the system "if this data is actually a String, then set my variable to that value. If not (??), here's the default value to use instead"
The problem comes when you introduce custom types. The system doesn't inherently know that your item is an Ingredient or Step. So, the cast fails, and you get the default value of [].
You have two options:
Use a custom type for your entire document (see Firebase documentation on this here: https://firebase.google.com/docs/firestore/query-data/get-data#swift_3). This SO question is also relevant: How to convert document to a custom object in Swift 5?
Convert the [String:Any] dictionary (or array of dictionaries as it may be in this case) yourself. First step might be to print data["ingredients"] to the console to see what it really has in it. Without being able to see what you actually have in Firestore, Let's assume it is a [[String:Any]] (an array of dictionaries). Then your conversion might look like this:
let ingredients = (data["ingredients"] as? [[String:Any]]).map { item in
return Ingredient(id: item["id"] as? String ?? "", name: item["name"] as? String ?? "", amount: item["amount"] as? String ?? "")
}
You can also experiment with using Codable, which could allow you to automate some of this process, say with JSONDecoder to do some of the work for you. Relevant SO: How can I use Swift’s Codable to encode into a dictionary?

Instead of running a snapshot for all users, how do you set up multiple queries to limit the number of users sent to the device?

What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.
What I need: The array of end users to be used in a .query in the line preceding the snapshot.
Why do I need this: This line is so that the entire database of users is not run on the client.
More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.
JSON of DB
"people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
"Coordinates" : {
"latitude" : -25.809620667034363,
"longitude" : 28.321706241781342
},
"PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
"caption" : 1602596281762, /// this is timestamp
"postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
"e1" : “cvvvv666",
"e2" : "aol.com",
" "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
"users" : "cvvvv666#aol.com"
},
.
var dict = CLLocation()
...
dict = CLLocation(latitude: lat, longitude: lon)
...
let thisUsersUid = Auth.auth().currentUser?.uid
//this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)
refArtists2.observe(DataEventType.value, with: { snapshot in
if snapshot.childrenCount>0{
self.people.removeAll()
for people in snapshot.children.allObjects as! [DataSnapshot] {
if people.key != thisUsersUid {
let peopleObject = people.value as? [String: AnyObject]
let peopleCoordinates = peopleObject?["Coordinates"] as? String
let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
let peoplepostID = peopleObject?["postID"] as? String
let coordSnap = people.childSnapshot(forPath: "Coordinates")
guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return }
guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return }
let locCoord = CLLocation(latitude: lat, longitude: lon)
let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
//let secondsInDay = 86400
**if Calendar.current.isDateInToday(date)** {
let distance = locCoord.distance(from: self.dict)
print(distance, "distancexy")
**if distance/1609.344 < 3000**{
let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
self.people.append(peopl)
let d = people.key as! String
self.printPersonInfo(uid:d) ///////This is used to reload the data
} else {
print ("w")
}
} else {
print ("alphaaa")
}
}
print("aaaaaaaa", self.people.map {$0.distance})
}
self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) } ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.
}
})
} else {
print("no")
}
})
Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.
Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?
The timestamp is seconds since 1970
My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).
var ppp: String! ////this should be the uid of all users in db
let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: { snapshot in
let captionss = snapshot.value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)
let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))
Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.
The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.
Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.
Suppose we have a users collection with user documents - each documentId is the users uid
users
uid_0
name: "Leroy"
and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.
posts
post_id
postText: "pretty flowers"
postDate: "20201103"
postUrl: "www....."
postUid: "uid_0"
postTopic: "flowers"
Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.
To do this we will need a compound query and then a subquery to retrieve the posters name as well.
func getTodaysPostsAboutFlowers() {
let postsCollection = self.db.collection("posts")
let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
query.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
for doc in docs {
let postText = doc.get("postText") as? String ?? "No text"
guard let postersUid = doc.get("postUid") as? String else { return }
self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
}
})
}
The above performs a compound query on both the postDate field as the postTopic field.
The above then calls another function to retrieve the users name and output both the name and what they said
func outputPostTextAndUserName(withText: String, andUid: String) {
let usersCollection = self.db.collection("users")
let theUserDoc = usersCollection.document(andUid)
theUserDoc.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = documentSnapshot {
let postersName = doc.get("name") as? String ?? "No Name"
print("\(postersName) posted: \(withText)")
}
})
}
and the output
Leroy posted: pretty flowers
As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.
Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.
people
uid_x
timetamps: 9023490823498 //Date(timeIntervalSince1970:
todaystamp: "20201106" // yyyymmdd format
that makes querying for nodes that contain today very simple.

Fatal error nil when unwrapping optional when calling from Firebase

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)")
})

Binary operator '??' cannot be applied to operands of type 'AnyObject?' and 'String'

I have done below code with Swift 2.2, but when switched to Swift 3.0 getting error at if condition "Binary operator '??' cannot be applied to operands of type 'AnyObject?' and 'String'"
if let custID = dataDict["cust_id"] ?? "",
let custName = dataDict["cust_name"] ?? "",
let fileName = dataDict["filename"] ?? "",
let transNumber = dataDict["trans_no"] ?? "" {
linesheet_custID = (custID["text"] ?? "" ) as! String
linesheet_custName = (custName["text"] ?? "" ) as! String
linesheet_filename = (fileName["text"] ?? "" ) as! String
linesheet_TransNumber = (transNumber["text"] ?? "" ) as! String
}
Please suggest solution ,as in above code in if let statement if dictionary value returns nil then i assigned blank string as ("") for particular key
You should cast the values you get from the dictionaries to optional Strings.
For example:
let custID = (dataDict["cust_id"] as String?) ?? ""
Do this:
let custID = dataDict["cust_id"] as? String ?? ""
I ran into the same error with a Date object in Swift 3.
The compiler seems to be OK with this:
let noStartDate = "No start date"
let description = "(\(self.startDate?.toString() ?? noStartDate)) - \(campaignNotes)"