Check Firebase for existing data before overwriting? - ios

I have the following data in Firebase:
devices
iphone5
date_created: "1456183905"
I'm trying to determine if "date_created" exists, and if it doesn't then create it.
I read about snapshots, but is there an easier way to check Firebase to see if this data exists? What I have now is using snapshots, but it is tied to an event handler. Can't I just do a basic query to see if this entry exists or not?
Thanks.

You can test if a value exists in your Swift code, by:
let ref = Firebase(url: "https://stackoverflow.firebaseio.com/35570687")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
if (!snapshot.exists()) {
ref.setValue([".sv": "timestamp"])
}
else {
print("already exist")
}
})
But since this is only client-side, there is a chance that two clients will run the code at almost the exact time and both end up writing the timestamp. In the snippet above that wouldn't be a problem, but in real use-cases this sort of race condition might be unwanted.
As Andre commented, you can validate this in your security rules:
"date_created": {
".write": "!data.exists() || data.val() == newData.val()"
This validates that either this is the first time you write date_created (so the data won't exist yet) or otherwise that the value is unchanged.

Related

Check if a Firestore query (whereField isEqualTo) did find no documents

I want to check if my Firestore query did find any documents with the specific fields I want or not. If not I would like to proceed to some other code.
Unfortunately I haven't found a solution myself to this problem. Can you help?
Code:
Firestore.firestore().collection("conversations").whereField("mainUserID", isEqualTo: MainUID)
.whereField("otherUserID", isEqualTo: otherUserId).getDocuments { (snapshot, err) in
if snapshot.exists == true { // Value of type 'QuerySnapshot?' has no member 'exists'
} else {
}
}
From the docs
A FIRQueryDocumentSnapshot contains data read from a document in your
Firestore database as part of a query. The document is guaranteed to
exist and its data can be extracted with the data property or by using
subscript syntax to access a specific field.
A FIRQueryDocumentSnapshot offers the same API surface as a
FIRDocumentSnapshot. As deleted documents are not returned from
queries, its exists property will always be true and data: will never
return nil.
with the important bit being this
The document is guaranteed to exist
so therefore a .exists option would not make sense due to the guaranteed existence of the snapshot.
One approach is to check how many documents are in the snapshot
if docs.count > 0 {
//there are docs
} else {
//there are no docs
}

Firebase Firestore Swift, Timestamp but server time?

With Firestore, I add a timestamp field like this
var ref: DocumentReference? = nil
ref = Firestore.firestore()
.collection("something")
.addDocument(data: [
"name": name,
"words": words,
"created": Timestamp(date: Date())
]) { ...
let theNewId = ref!.documentID
...
}
That's fine and works great, but it's not really correct. Should be using the "server timestamp" which Firestore supplies.
Please note this is on iOS (Swift) and Firestore, not Firebase.
What is the syntax to get a server timestamp?
The syntax you're looking for is:
"created": FieldValue.serverTimestamp()
This creates a token which itself has no date value. The value is assigned by the server when the write is actually written to the database, which could be much later if there are network issues, so keep that in mind.
Also keep in mind that because they are tokens, they can present different values when you read them, to which we can configure how they should be interpreted:
doc.get("created", serverTimestampBehavior: .none)
doc.get("created", serverTimestampBehavior: .previous)
doc.get("created", serverTimestampBehavior: .estimate)
none will give you a nil value if the value hasn't yet been set by the server. For example, if you're writing a document that relies on latency-compensated returns, you'll get nil on that latency-compensated return until the server eventually executes the write.
previous will give you any previous values, if they exist.
estimate will give you a value, but it will be an estimate of what the value is likely to be. For example, if you're writing a document that relies on a latency-compensated returns, estimate will give you a date value on that latency-compensated return even though the server has yet to execute the write and set its value.
It is for these reasons that dealing with Firestore's timestamps may require handling more returns by your snapshot listeners (to update tokens). A Swift alternative to these tokens is the Unix timestamp:
extension Date {
var unixTimestamp: Int {
return Int(self.timeIntervalSince1970 * 1_000) // millisecond precision
}
}
"created": Date().unixTimestamp
This is definitely the best explanation of how the timestamps work (written by the same Doug Stevenson who actually posted an answer): https://medium.com/firebase-developers/the-secrets-of-firestore-fieldvalue-servertimestamp-revealed-29dd7a38a82b
If you want a server timestamp for a field's value, use FieldValue.serverTimestamp(). This will return a token value that gets interpreted on the server after the write completes.

data not being saved in firebase database

I am just starting out with Firebase and have managed to send data to a Firebase Realtime Database. The problem is that some times it works and sometimes not. I am struggling to understand why.
Here is a code snippet
var pq_data = jsPsych.data.get().values();
for (var ix= 0; ix < pq_data.length; ix++){
var object=pq_data[ix];
var pq_boo = pq_database.ref(subj_id +ix.toString()+'/').update(object)
}
As I say this works sometimes but not always and I understand that it may have something to do with the code completing before the write operations have(?)
I have read but do not clearly understand advice about onCompletion and I am still in the dark. I need to make sure each object is written to the database - is this possible and if so how?
Very much a beginner,
Philip.
// Import Admin SDK
var admin = require("firebase-admin");
// Get a database reference to our blog
var db = admin.database();
var ref = db.ref("server/saving-data/fireblog");
First, create a database reference to your user data. Then use set() / setValue() to save a user object to the database with the user's username, full name, and birthday. You can pass set a string, number, boolean, null, array or any JSON object. Passing null will remove the data at the specified location. In this case you'll pass it an object:
var usersRef = ref.child("users");
usersRef.set({
alanisawesome: {
date_of_birth: "June 23, 1912",
full_name: "Alan Turing"
},
gracehop: {
date_of_birth: "December 9, 1906",
full_name: "Grace Hopper"
}
});
Thanks for this Yes For brevity I didn't show all the code and so I have access to the database. The problem is with sending data to it. Sometimes it works and sometimes not.
I now realise this is related to my failure to deal with Promises. I have now some understanding of these but still need to make sure that the data gets captured in the database. SO even though the Promise may return an Error I still need to re-send the data so that it will get written to the database. Still not sure whether this is advisable or even possible.

firestore allow write for only one field

I have the following security rule set up in Firestore:
match /ads/{adId} {
allow read: if true;
allow write: if (request.auth.uid == request.resource.data.ownerId) ||
(request.auth != null &&
request.resource.data.size() == 1 &&
request.resource.data.keys().hasAll(['markedFavoriteBy']));
}
The owner of the document has all write permissions but other users can only write in one field - which is called 'markedFavoriteBy'. It seems to work in the Firestore simulator but not in Xcode (with iPhone simulator).
Important to note that the path does not go down to the document field - just to the document. I know that it is not possible to attach a security rule to a single field. However, with this workaround it should still work.
The iOS client code to update the field is the following:
let adRef = Firestore.firestore().collection("ads").document(ad.key!)
let value: FieldValue = ad.isFavorite ? FieldValue.arrayUnion([uid]) : FieldValue.arrayRemove([uid])
adRef.updateData([DatabaseKeys.markedFavoriteBy : value]) { (error) in
if let error = error {
print(error.localizedDescription)
}
}
The error printed to the console is of course: "Missing or insufficient permissions."
I can't find an error in the code - is this a bug that is somehow related to the very recent iOS Firestore SDK update? the functions arrayUnion and arrayRemove have been added only in that recent update.
Sanity check: I have changed the write rule to
allow write: if true;
Result: The array 'markedFavoriteBy' can be changed without any problem.
thanks for any hints in the right direction.
I'm not able to reproduce the entire scenario as I'm not sure of the actual contents of your database. However, what I suspect is the problem is that it doesn't grant permission because request.resource.data represents the new version of the entire document (which will be stored after the update is approved).
You probably want to use request.writeFields doc
Bear in mind that request.writeFields is not available in the simulator.

Search Firebase Query with array of unique keys

I have my schema set as follows.
Now i want to send the a string chinohills7leavescafe101191514.19284 and want to check if there is any string in the Chino Hills or not.
I am confused to make any search query because i have not stored above string in fixed childKey
I know the schema should be like this but i cannot change the schema.
leaves-cafe
codes
Chino Hills
-Kw0ZtwrPjyNh1_HJrkf
codeValue: "chinohills7leavescafe101191514.19284"
You're looking for queryOrderedByValue. It works the same ways as queryOrderedByChild and allows you to use queryEqualToValue to achieve the result you need since you can't alter your current schema.
Here's an example
// warning: untested code - just illustrating queryOrderedByValue
let ref = Database.database().reference().child("leaves-cafe").child("codes").child("Chino Hills")
let queryRef = ref.queryOrderedByValue().queryEqual(toValue: "chinohills7leavescafe101191514.19284")
queryRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("value does exists")
} else {
print("value doesn't exist")
}
})
Your alternative option is to iterate over all nodes and check if the value exists manually as 3stud1ant3 suggested. However, note that this approach is both costy and a security risk. You would be downloading potentially a lot of data, and generally you shouldn't load unneeded data (especially if they're sensitive information, not sure if that's your case) on device; it's the equivalent of downloading all passwords off a database to check if the entered one matches a given user's.

Resources