I have some code in my app, some of which I found online in a tutorial somewhere. Everything seems to work okay, but I don't understand something. In the line that starts "let dataDescription = document...." I don't understand what exactly that is doing, since "dataDescription" is never subsequently used anywhere. I understand that "document.data().map..." is retrieving the data from the document, but then "dataDescription" is never used anywhere after that. I just want to understand what's happening. I do find a lot of code online to use, but I hate just using it without understanding what's going on.
let docRef = db.collection("jammers").document(userDocumentID)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
let data = document.data()
//Code for saving or deleting Sings field
if (data!["Sings"] != nil) //If Sings field exists
{
if self.singsSwitch.isOn //If the switch is set to on
{
//Update Sings field
docRef.updateData(["Sings": true])
}
Related
I'm a little bit confused about addSnapshotListener and getDocuments. As I read in the firebase docs, getDocuments() is retrieving data once and addSnapshotListener is retrieving in real-time.
What I want to ask.
If I'm using getDocuments, and im changing some documents in the Firestore , it will not make the change in the app ? But if im using addSnapshotListener it will ?
I'm making an delivery app, which is the best to use to store pictures of food , descriptions etc.
This is what im using to retrieve labels and pictures from my app :
db.collection("labels").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let newEntry = Labels(
firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String,
photoKey: data["photoKey"] as! String
)
self.labels
.append(newEntry)
}
}
DispatchQueue.main.async {
self.tableViewTest.reloadData()
}
getDocuments will return results one time, with the current Firestore data.
addSnapshotListener will return an initial result set (same as getDocuments) and get called any time that data changes.
If your data is modified in Firestore and you've used getDocuments, your app will not be notified of those changes. For example, in your delivery app, perhaps the item goes out-of-stock while the user is using it. Or, the price gets changed, the user is logged in from another device, etc -- many possibilities for why the data might change. By using a snapshot listener, you'd get notified if any of these changes happen.
However, if you're relatively confident you don't need updates to the data (like getting a user's address from the database, for example), you could opt to just use getDocuments.
I have an app that uses a snapshot listener to listen to data in a particular document. However, when a field in the document is updated, the data is read 7-10x over. Never read once, and never read the number of fields that are in my document, it always seems to be an arbitrary number. Also, when the read data prints out, it seems like every printout is the same except for a couple of fields that I'm not setting (like an array prints out "<__NSArrayM 0x282d9f240>" but the number changes on each print). As a result, minimal usage of my app is causing 5-10k reads. I'm trying to reduce the number of reads and I don't know exactly how, but the app has to read as data is updated, but my two questions are:
when I print the data from the listener, does each data print out signify a separate read operation? and
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated, then perform one read instead of multiple reads every time any field is updated? Or another strategy to reduce reads when multiple writes occur?
Not sure if this is helpful, but here is the code I'm using to perform the read...its pretty much the standard code from the firestore sdk:
env.db.collection(env.currentSessionCode!).document(K.FStore.docName).addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
self.env.data1 = data[K.FStore.data1] as? String ?? "????"
self.env.data2 = data[K.FStore.data2] as? String ?? "????"
self.env.data3 = data[K.FStore.data3] as? [String] ?? ["????"]
self.env.data4 = data[K.FStore.data4] as? [String] ?? ["????"]
self.env.data5 = data[K.FStore.data5] as? Double ?? 0
self.env.data6 = data[K.FStore.data6] as? Double ?? 0
self.env.data7 = data[K.FStore.data7] as! Bool
self.env.data8 = data[K.FStore.data8] as! Bool
print("Current data: \(data)")
Update - For clarification, the way I have been updating my data to firebase is with a environment object, and using "didSet" when the new data is changed/updated in the environment to update it on firebase...I think this might be the root of the problem, as the function called on didSet runs 4-5 times each time it is called...
relevant code:
#Published var data1: String {
didSet {
postValuesToFB(fb: K.FStore.data1, string: data1)
}
}
func postValuesToFB(fb: String, string: String) {
guard let code = currentSessionCode else {
fatalError("Error - Connection Check - no value for current session code in Global Env")
}
let docRef = db.collection(code).document(K.FStore.docName)
docRef.getDocument { document, _ in
guard let document = document else {
return
}
if document.exists {
let session = self.db.collection(code).document(K.FStore.docName)
session.updateData([
fb: string,
K.FStore.dateLastAccessed: FieldValue.serverTimestamp(),
])
return
}
}
}
Based on your comments, it sounds as if you've written no code to remove a listener after it's been added. Based on this, it's relatively safe to assume that your code could be adding many listeners over time, and each one is getting called for each change.
You should take a moment to think about the architecture of your app and figure out when is the appropriate time to remove listeners when they're no longer needed. Usually this corresponds with the lifecycle of whatever component is responsible for display of the data from the query. Review the documentation for getting realtime updates, especially the section on detaching a listener. It's up to you to determine the right time to remove your listener, but you definitely don't want to "leak" a listener as you are now.
A common source of unexpected read charges for developers who are new to Firestore is the Firebase console itself. When that console displays Firestore content, you are charged for those read too. To ensure you measure the impact of your code correctly, test it with the Firebase console closed.
when I print the data from the listener, does each data print out signify a separate read operation?
Not really. You get charged for a document read, when the document is read on your behalf on the server. You are not charted for printing the same DocumentSnapshot multiple times.
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated
Nope. To know the document has changed, the server needs to read it. So that requires a charged read operation.
I'm still relatively a noob and I'm having a bit of a hard time figuring out this bug.
I'm working on a gym logging app that is quite basic and consists of two TableViewControllers inside of a NavigationStack. I'm using Firebase/Firestore as my backend and I have snapShotListeners setup for each viewController for when new data is added or removed.
The bug I'm running into is a bit hard to explain, but I'll try my best...
First, I create two new documents in my firstTableViewController named "A" and "B" and then within each of those documents I create another two documents in my secondViewController named "C" and "D".
Next, I delete documents "A" and "B" in my firstTableViewController, which also deletes the two pairs of documents "C" and "D" automatically. All these changes are observed and reflected on the Firestore backend, so no problems, everything works the way it's supposed to work.
At this point I'm back at square one with two empty TableViewControllers. So now I try to do the exact same thing that I just did by re-creating the two documents that I created earlier, with the exact same names "A" and "B", and I also attempt to re-create documents "C" and "D" within "A" and within "B" respectively. However this time, when I try to re-create document "D" within document "B", my diff.type == .removed gets executed within my snapShotListener on my secondViewController for some reason, and I get the Unexpectedly found nil while unwrapping an Optional value code...
I have no idea why this happens and it's breaking my head. Any help or guidance would be much appreciated. Again, I'm quite new to this, so my debugging skills are not quite to par just yet. I'd be happy to include code, but I'm not exactly sure of how much code I should reveal...please let me know.
Thank you in advanced!
EDIT:
If I only have two documents ("C" and "D") in my SecondViewController, when I enter that VC, my diff.type == .added get's executed 4 times instead of 2. It prints "Document Added" 4 times even though there are only 2 documents in that collection. Here is the code...
//MARK: - Load the Data
func loadExercises() {
feedback = self.exerciseCollection!.whereField("Workout", isEqualTo: selectedWorkout!.workout).order(by: "Timestamp", descending: false).addSnapshotListener({ (querySnapshot, err) in
let group = DispatchGroup()
guard let snapshot = querySnapshot else {return}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
self.exerciseArray.removeAll()
group.enter()
for document in querySnapshot!.documents {
let workoutData = document.data()
let exercise = workoutData["Exercise"] as! String
let newExercise = Exercise(Day: self.selectedWorkout!.day, Workout: self.selectedWorkout!.workout, Exercise: exercise, Key: document.reference)
self.exerciseArray.append(newExercise)
print("Document Added")
}
group.leave()
group.notify(queue: .main){
self.tableView.reloadData()
}
}
if (diff.type == .removed) {
print("Document Removed")
self.tableView.deleteRows(at: [self.indexToRemove!], with: .automatic)
}
}
}
)}
I am building an app that incorporates Firebase and need to be able to be able to add/and or set data at the top of the my database. Currently whenever I add data it places it randomly under the parent node. I've seen some Objective C answers to this but they haven't really made to much sense. Sorry for the lack of code, but I don't know where exactly where to start on this issue. Any help would be great!
When you say it is getting placed 'randomly' under the parent, I think this is the childByAutoId() method getting called. While the name of the nodes appears randomly (e.g. -K6tdghsbci7g8), it will actually ensure that data is added under a given parent node in order. This is very useful for adding new data to lists. For example:
let pointlessData:String = "some data"
ref.childByAutoId().setValue(pointlessData)
// creates a new ordered child node under the `ref` with "some data" as the value
There isn't really a way to order the nodes back to front in this fashion, although you could order negatively by timestamp by adding a negative timestamp to each node you add then order your results using ref.queryOrderedByChild("negativeTimestamp") such that the values you get are the newest first.
let path = FirebaseBaseUrl + "dialogs/" + self.receiverId + "/" + self.senderId
let chilRef = FIRDatabase.database().referenceFromURL(path)
let refChild = chilRef.childByAutoId()
let dic = NSMutableDictionary()
dic .setValue(text, forKey: "text")
dic .setValue(FIRServerValue.timestamp(), forKey: "timestamp")
dic .setValue(true, forKey: "your")
refChild.updateChildValues(dic as [NSObject : AnyObject]) { (error, ref) in
if(error != nil){
print("Error",error)
}else{
print("\n\n\n\n\nAdded successfully...")
}
}
I was reviewing "old" code (not that old, but a developer went away and we are documenting and reviewing his code), when, in the context of a iOS Share Extension, I found the following two lines:
let content = self.extensionContext!.inputItems[0] as! NSExtensionItem
for attachment in content.attachments as! [NSItemProvider] {
The first line: I red the docs and found inputItems can be empty too, so I suppose that forced cast will crash the app if this thing should happen (I don't know exactly how, but maybe it could).
The second line: same as above, with the difference that if you have no crash in the first line you probably won't have another here.
Question 1: is it a good idea to check the length of inputItems before the loop?
Question 2: I made a little edit to this code and I changed the first line to this:
let content = self.extensionContext!.inputItems[0] as? NSExtensionItem
After doing so, XCode suggests a correction to the second line I don't like very much (I consider it not readable):
for attachment in (content?.attachments as? [NSItemProvider])!
is the XCode suggestion the way to go?
Any comment is appreciated. Thanks!
It's always a good idea to unwrap optionals before accessing the object itself.
You can use guard to unwrap the optional chain before proceeding to work with the content.
guard let content = self.extensionContext?.inputItems.first as? NSExtensionItem else { return }
guard let attachments = content.attachments as? [NSItemProvider] else { return }
for attachment in attachments {
// Do stuff
}
Resources:
Statements
Patterns
You might want to review the documentation for Swift optionals
Forced unwrapping will crash the app when encountering nil values which are different from empty arrays.
Question 1: No, you don't have to, for-in loops account for the length of the array. Also, the code will loop over content.attachments not inputItems.
Question 2: Your edit of the first line caused content to become an optional value, requiring some kind of unwrapping in the second line
If you can be sure the casts will always work the way your developer had it is fine. If you want more safety I would probably do:
guard let content = self.extensionContext?.attachments.first as? NSExtensionItem,
let attachments = content.attachments as? [NSItemProvider] else
{
// fatalError()
return
}
for attachment in attachments
{
//
}