how to observe property changes of Backendless database ? - Swift - ios

I have this code in Swift 3 to get backendless user so I can get his/her properties:
let whereClause = "objectId = '\(userId)'"
let query = BackendlessDataQuery()
query.whereClause = whereClause
let data = Backendless.sharedInstance().persistenceService.of(BackendlessUser.ofClass())
data?.find(query, response: { (result) in
let user = result?.data.first as! BackendlessUser
myLabel.text = user.getProperty("firstName") as! String
})
The code is working fine but my question is how to observe the property changes ? is there a way if the value of property firstName changed I can update my label automatically ?

The use-case you describe is not really as simple as it may seem, but it's definitely possible.
You can capture any changes in Users table by creating an afterUpdate event handler. From there you could publish a message to some dedicated channel in messaging service.
At the same time, your application should be subscribed to this same channel. This way you could update your UI when the appropriate message is received.

Related

Which to use, addSnapshotListener() or getDocuments()

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.

Firestore in Swift - Listener producing needless reads

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.

Swift Firebase -How to generate different .childByAutoId keys when using Fan Out

I have a chat system inside my app. When the user presses send to send the message data to different nodes inside the database -it works fine. The issue I'm having is since I'm using fan out I generate the .childByAutoIdkey before the data is sent. The user presses a send button to start the process but it's always the same exact .childByAutoId key so I'm just overwriting the previous message data. If the user pops the vc and comes back to it then a new key is created but obviously that's terrible ux for a messaging system?
How can I generate different .childByAutoId keys every time the user presses send to fan out?
#obj func sendMessageButtonPressed() {
// ***here's the problem, every time they press send, it's the same exact childByAutoId().key so I'm just overwriting the previous data at the messages/messageId path
guard let messageId = FirebaseManager.Database.database().reference().child("messages")?.childByAutoId().key else { return }
var messageIdDict: [String: Any] = [messageId: "1"]
var messageDict = [String: Any]() // has the fromId, toId, message, and timeStamp on it
let messageIdPath = "messages/\(messageId)"
let fromIdPath = "user-messages/\(currentUserId)"
let toIdPath = "user-messages/\(toId)"
var fanOutDict = [String: Any]()
fanOutDict.updateValue(messageDict, forKey: messageIdPath)
fanOutDict.updateValue(messageIdDict, forKey: fromIdPath)
fanOutDict.updateValue(messageIdDict, forKey: toIdPath)
let rootRef = Database.database().reference()
rootRef?.updateChildValues(fanOutDict)
}
The problem wasn't a new key was not getting generated. #FrankvanPuffelen pointed out in th comments that a new key should get generated every time which is exactly what was happening.
The problem was the fanout was overwriting what was originally written at these 2 paths:
let fromIdPath = "user-messages/\(currentUserId)"
let toIdPath = "user-messages/\(toId)"
It appeared the key was the same because the data kept getting overwritten.
The way I was generating the key works fine

Receive limited query with custom start point

I'm building a basic chat app with swift for iOS with firebase realtime database.
The Messages are observed with a limit for the least 10.
Now, I want to implement the functionality of loading earlier send messages. Currently I'm trying to achieve this by using this function:
let query = threadRef.child("messages").queryOrderedByKey().queryStarting(atValue: "2").queryLimited(toLast: 2)
Which returns this query:
(/vYhNJ3nNQlSEEXWaJAtPLhikIZi1/messages {
i = ".key";
l = 2;
sp = 2;
vf = r;
})
And this should give me the data:
query.observeSingleEvent(of: .value, with: { (snap) in
But it just limits the query and not set the start point to a specific position.
Here is the firebase database structure:
messages
-Kgzb3_b26CnkTDglNd8
date:
senderId:
senderName:
text:
-Kgzb4Qip6_jQdKRWFey
-Kgzb4ha0KZkLZeBIaxW
-Kgzb577KlNKOHxsQo9W
-Kgzb5cqIVMhRmU019Jf
Anyone have an idea on how to implement a feature like that?
Okay I finally found a way to do what I wanted.
First of all I misunderstood the way to access data from Firebase.
This is now how I get the query:
let indexValue = messages.first?.fireBaseKey
let query = messageRef.queryOrderedByKey().queryEnding(atValue:indexValue).queryLimited(toLast: 3)
1) get the FireBase key I previously saved to my custom chat messages
2) construct the query:
order it by key
set the ending to oldest message
limit the array to query to desired length
Then to actually get the query I used:
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children.dropLast().reversed() {
let fireSnap = (child as! FIRDataSnapshot)
//do stuff with data
}
})
1) get the query as a single event
2) iterate over children and I needed to dropLast() to make sure I don't have any duplicated messages and reverse it to get the correct order.
3) cast the current child as a FIRDataSnapshot to access the data
Since I couldn't find a simple example for this so I thought I leave my solution here incase other people running into the same problem.

iOS Swift: retrieve an entry from the database for the user currently logged in

How do I retrieve a value (other than username and user id, which seem easier to get) for the current user from the database.
Ironically, I can set the value as follows and that works just fine:
let databaseRef = FIRDatabase.database().reference()
userID = (FIRAuth.auth()?.currentUser?.uid)! as String
databaseRef.child("users").child(userID!).child("TermCond").setValue("Yes")
But for the life of me I cannot work out what to put instead of setValue if I simply want to retrieve the current TermCond value. I thought just using value as for example in
let DesiredValue = databaseRef.child("users").child(userID!).child("TermCond").value as? String
Would suffice, but nothing works. I am confused why retrieving the value should be more difficult than setting it.
To "read" a value from Firebase, you need to add a reference listener that gets called every time that value changes.
In your case, that could be something like:
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("users").child(userID!).child("TermCond").observe(FIRDataEventType.value, with: { (snapshot) in
let desiredValue = snapshot.value as? String
})
This block of code will get triggered every time your value changes. If you only want to read it once, you can use observeSingleEvent:of:with instead of observe:with.
This is as described in the Firebase documentation: https://firebase.google.com/docs/database/ios/read-and-write
I recommend you read their entire Documentation to get an idea of how Firebase works, as it is very different from traditional databases.
I can also recommend the following tutorial if you'd like to learn a bit more about the Firebase Database and how it works: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
I've solved this now (based on Aleksander's reply). The way I did it is as follows.
databaseRef.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
self.desiredValue = value?["TermCond"] as? String ?? ""
self.LabelToShow.text = self.desiredValue!
}) { (error) in
print(error.localizedDescription)
}
This works absolutely fine and shows the value of TermCond in the LabelToShow on my iOS screen.

Resources