observeSingleEvent(of:with:) fetching old data - ios

Background:
I have an app where new users complete a sign-up form (consisting of a username, email, and a password) to register for an account. When the new user submits the form, the app checks its Firebase Database to see if the username has already been taken. The problem here is that observeSingleEvent(of:with:) is not getting the newest data. When I update the data directly from the Firebase console, the changes are not reflected back in the returned results of observeSingleEvent(of:with:). Even between app restarts, the new data changes are not returned.
The only thing I've seen on the issue is here. A user says not to have persistence enabled when using observeSingleEvent(of:with:). But, I have persistence disabled. Also, I set the keepSynced() on the FIRDatabase instance to false (and I've tried true as well). No luck with either setting.
Here's an example of what I am trying:
let usersDatabaseReference = FIRDatabase.database().reference().child("users")
usersDatabaseReference.keepSynced(false)
// Query the database to see if the username is available
// The user with the username "mark" was removed from the database via the Firebase console
let username = "mark" // This user used to exist in the database (and was previously added through the app).
usersDatabaseReference.child(username).observeSingleEvent(of: .value, with: { (snapshot) in ... } )
The above code block should return the newest data (one where the user "mark" should not exist).
I am wondering if anyone else ran into this problem, or if anyone has possible solutions.
Note: Developing using iOS 10.1 and swift 3.

the observeSingleEvent(of:with:) return the value only one time
try this :
refHandle = postRef.observe(FIRDataEventType.value, with: { (snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
// ...
})
this will add a listener on database event link

I recommend you refactor your JSON data model using the username as key. Doing this, you should get an error when trying to create a duplicate user in the database.

So, my problem had nothing to do with caching or fetching old results. Some of my code logic depended on checking the value of the snapshot parameter that was passed back in the completion handler for observeSingleEvent(of:with:). I wrongly assumed that I could check for a null database value by comparing it with nil; this led to a misinterpretation of results.
The best way to actually check for a null database fetch is by calling the exists() method on a FIRDataSnapshot instance. exists() returns true if the snapshot contains a non-null value.

Related

Firebase doesn't call back when using '.childAdded'

I have the following code, written in Swift which I expect to be called each time a new record has been added to the my Database:
var databaseRef = Firebase()
databaseRef = Firebase.init(url: "<MY_PROJECT_URL>")
databaseRef.child(byAppendingPath: "channels").queryLimited(toFirst: 100).observe(.childAdded , with: { (snapshot) in
print("New Message input by user")
})
And this is my data structure:
So I basically create a listener for the branch 'channels'. The completion handler gets called only at the start of my program, then, never again. The strange thing is that if I use '.value' rather than '.childAdded' it does work! I am not interested in using '.value' since that returns me the whole chunk of data inside the 'channels' branch and I am really interested only in the single record that was added. (actually one of those L1... guys. A new one of course) Any ideas?
If you are observing messages from all users at the same time I'd recommend restructuring your Firebase data a bit. Make messages a top-level object, and then you can just observe that whole section. I don't know what your messages look like, but I assume if you do this you will need to add a reference to the user inside the message so that you can get the new message added and still see which user wrote it.

Firebase: how to retrieve only changed items next time app launches?

I've got an app that uses a Firebase db containing 100,000 items. My app has to process through each of these items which takes several seconds.
What is happening is that every time the app is launched (from a terminated state) those 100,000 items are being processed each time (even if the contents of the db on the Firebase server have not changed). Obviously, I don't want the app to do this if not necessary. Here's some code:
if dbRef == nil {
FirebaseApp.configure();
Database.database().isPersistenceEnabled = true
...
let dbRef = Database.database().reference(withPath: kFirebaseDBName)
_ = spamRef.observe(DataEventType.value, with: { (theSnapshot) in
if let content = theSnapshot.value as? [String : AnyObject]
{
self.processContent(content: content)
}
Each time the app is started then the content snapshot contains the entire database reference contents.
Is there a way of, for example, getting the last date the database was updated (on the server), or only obtaining the delta of changed items between each app launch - can a query return just changed since last queried for example, or something similar?
I don't know how many items have changed so cannot call something like:
queryLimited(toLast: N))
As I don't know what value N is.
I've tried adding keepSynced as follows in the hope it might change things, but no.
if dbRef == nil {
FirebaseApp.configure();
Database.database().isPersistenceEnabled = true
...
let dbRef = Database.database().reference(withPath: kFirebaseDBName)
dbRef.keepSynced(true)
_ = dbRef.observe(DataEventType.value, with: { (theSnapshot) in
if let content = theSnapshot.value as? [String : AnyObject]
{
self.processContent(content: content)
}
I have no idea how much data might have changed so don't know what value to supply to something like toLast or similar to modify the observation parameters.
The database (which was not created nor updated with new content by me) has 100,000 items in a flat structure (i.e. one parent with 100,000 children) and any number of these children in any order might have been deleted and replaced since last time my app ran, but the total will still be 100,000. None of the children have an explicit timestamp or anything like that.
I was under the impression if Firebase kept a local cache of the data (due to isPersistenceEnabled) then next time it connects with the server it would only sync what had changed on the server. Therefore in order to do this Firebase itself must internally have some delta information somewhere, so I was hoping that delta information may available in some form to my app.
Note: My app does not need persistence to be enabled, the above code is doing so just as variations to see if anything will result in the behavior I desire with the observer.
UPDATE
So looking at the documentation more you can set a timestamp for the last time a user was connected to the server using:
lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Take a look at this question Frank explains some issues with persistence and listeners. The question is for Android but the principles are the same.
I still think the problem is your query. Since you already have the data persisted .value is not what you want since this returns all of the data.
I think you want to attach a .childChanged listener to your query. In this case the query will only return the data that has been changed. If you haven't heard of .childChanged before you can read about it here.
I didn't realize this problem is specifically related to persistence. I think you are looking for keepSynced(). Take a look at this.
ORIGINAL ANSWER
The problem is your query. You are asking for all of the data that's why you're getting all of the data. You want to look into limiting your queries using toFirst or toLast. Additionally, I don't think you can query for the last time the database was updated. You could check the last node in your data structure if you have the timestamp saved, but you might as well just get the newest data.
You want something like this:
ref.child("yourChild").queryLimited(toLast: 7).observeSingleEvent(of: .value, with: { snap in
// do something
})
Depending on how you're writing your data you'll want toLast or toFirst. Assuming the newest data is written last toLast is what you want. Also note that the numbers I am limiting to are arbitrary you can use any number that fits your project.
If you already have a key and you want to start querying above that key you can do something like this:
ref.child("YourChild").queryOrderedByKey().queryEnding(atValue: lastVisiblePostKey).queryLimited(toLast: 8).observeSingleEvent(of: .value, with: { snap in
// do something with more posts
})
You may also want to look into this question, this question and pagination.

Only writing data to firebase if information doesn't exist - Swift iOS

I want to only update the values email, firstname and lastname if they are blank.
I need this so that if the user decides to change these in the settings, they are not overwritten every time the user logs in with facebook.
Any solutions to check if the fields are blank without a datasnapshot? Trying to maximise efficency.
Current code when user signs in with facebook?
Database Structure for each user:
One way to do this is using a firebase transaction.
A transaction allows you to check the current value of a DB reference before you set/update it. It's main use case is preventing multiple concurrent updates from multiple sources but it can be used for this case as well - read and then write.
In the transaction block you get the value of the DB ref you're transacting on & can check that the value is null (hence 'create' case) -> then update it as required and return TransactionResult.success(withValue: newData).
If the object is already set you simply abort the transaction with TransactionResult.abort() and no write to the DB is executed.
Another option, that doesn't require a read/write, is to set a Firebase database rule on the relevant ref that will only allow write if the previous value was null:
"refPath": {
".write": "data.val() == null && newDataval() != null"
}
Writing a second time to the DB for an existing ref will fail.
I'd go with the transaction - more expressive of the requirement in the client code.
In firebase the only way you have to check if the current value of your fields in your database are empty is to fetch them before you are setting them.
You can check the field is empty only by fetching them.Then Use this code to update a particular value
ref.child("yourKey").child("yourKey").updateChildValues(["email": yourValue])

Firebase observeEventType .childAdded doesn't work as promised

When I call this observe function from in my viewcontroller, the .childadded immediately returns a object that was already stored instead of has just bin added like .childadded would suspect.
func observe(callback: RiderVC){
let ref = DBProvider.Instance.dbRef.child("rideRequests")
ref.observe(DataEventType.childAdded) { (snapshot: DataSnapshot) in
if let data = snapshot.value as? NSDictionary {
let drive = cabRide(ritID: ritID, bestemming: bestemming,
vanafLocatie: vanaf, taxiID: taxiID, status: status)
print(drive)
callback.alertForARide(title: "Wilt u deze rit krijgen?", message: "Van: \(vanaf), Naar: \(bestemming)", ritID: ritID)
}
}
}
When I try this function with .childchanged, I only get a alert when it is changed like it suppose to do, but when doing .chiladded, it just gets all the requests out of the database and those requests were already there.
When I add a new request, it also gives an alert. So it works, but how can I get rid of the not added and already there requests?
Does anybody know this flaw?
This is working exactly as promised. From the documentation:
Retrieve lists of items or listen for additions to a list of items.
This event is triggered once for each existing child and then again
every time a new child is added to the specified path. The listener is
passed a snapshot containing the new child's data.
That might seem weird at first, but this is generally what most developers want, as it's basically a way of asking for all data from a particular branch in the database, even if new items get added to it in the future.
If you want it to work the way you're describing, where you're only getting new items in the database after your app has started up, you'll need to do a little bit of work yourself. First, you'll want to add timestamps to the objects you're adding to the database. Then you'll want to do some kind of call where you're asking to query your database by those timestamps. It'll probably look something like this:
myDatabaseRef.queryOrdered(byChild: "myTimestamp").queryStarting(atValue: <currentTimestamp>)
Good luck!

Can't get list of users [duplicate]

This question already has answers here:
How do I return a list of users if I use the Firebase simple username & password authentication
(7 answers)
Closed 6 years ago.
I want to show list of users in my app. I use default Auth system from firebase. But response always empty.
FIRDatabase.database().reference().child("users").queryOrdered(byChild: "email").observe(.value, with: { snapshot in
print(snapshot)
})
But snapshot is always Snap (users) <null>
The Firebase Authentication system does not automatically insert anything into the Firebase Database. I guess your database is empty, which is the reason for snapshot being null.
Your code looks correct, but as I said there might not be any data in your database to receive.
Depending on what exactly you want to achieve you should consider storing user meta data in you database. A good point to do so would be directly after the user creation.
Registering a user with Firebase Authentication, by default, does nothing to modify your Firebase Database. Authentication and Database are two pretty much unrelated services. It's a common practice, once you register a user, to save an entry in your Database with their uid, so you can relate the two services:
let auth: FIRAuth? = FIRAuth.auth() // The authentication object
auth?.createUser(withEmail: email, password: password) { (user, error) in
// If registration was successful, `user` is a FIRUser with a uid
if let userId = user?.uid {
let exampleDBPath = FIRDatabase.database().child("users").child(userId)
// Write the user object, for instance a user name or other data, to this path
exampleDBPath.setValue(someJSONAboutTheUser) { (error, result) in
// Now you have a spot to modify your user in the database
}
}
}
This FIRUser created from registration is the same type of object you'll get when a user tries to sign in, so you can find the correct user in the database via the same uid.

Resources