How can I retrieve data with queryEqualToValue in auto id child. Firebase-iOS - swift

As my question. I can't retrieve data from firebase when I try to use "queryEqualToValue" with auto id key.
self.ref.child(..my child..).queryOrderByChild("name").queryEqualToValue("my name")
auto child id above child "name".
Edit: for my json tree
Students
(auto id)
name
first name
nick name
My real data doesn't like this but this's for example structure.
I really want to check equal to "first name".
Here's my code
let ref = FIRDatabase.database().reference()
ref.child("Students").queryOrderedByChild("name").queryEqualToValue("Jason bourne").observeEventType(.Value, withBlock: { snapshot in
print("value : " + snapshot.value)
}

Given your realtime database looks something like this:
{
"students": {
1: {
"name": {
"first_name": "Nathapong",
"nick_name": "Oniikal3"
}
}
}
}
You can observe the students path with the event type ChildAdded and order the query by child key name/first_name. Then you can use queryEqualToValue to find students with a particular first name.
let ref = FIRDatabase.database().referenceWithPath('students').queryOrderByChild("name/first_name").queryEqualToValue("Nathapong")
ref.observeSingleEventOfType(.ChildAdded, block: { snapshot in
print(snapshot)
})

This code should work. Check for names if I made any mistakes and insert your data.
self.ref.child("Students").child("name").queryOrderedByChild("first name").queryEqualToValue("my name").observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
print(snapshot.value)
}

Before use queries with child, you need to specify this using ".indexOn":["name"] in Database Rules. See documentation for examples
{
"rules":{
".write": "true",
".read": "true",
"Students": {
"$key": {
".indexOn":["name"]
}
}
}
}
Your read/write rules can go where you need it. .indexOn is just like another rule, alongside with .validate

Related

how can I set the value of objectId to another property different that _id when creating a document?

I'm trying to create an object that looks like this:
const userSettingsSchema = extendSchema(HistorySchema, {
user: //ObjectId here,
key:{type: String},
value:{type: String}
});
this is the post method declared in the router
app.post(
"/api/user/settings/:key",
userSettingsController.create
);
and this is the method "create":
async create(request, response) {
try {
const param = request.params.key;
const body = request.body;
console.log('body', body)
switch (param) {
case 'theme':
var userSettings = await UserSettings.create(body) // user:objecId -> missing...
response.status(201).send(userSettings);
break;
}
} catch (e) {
return response.status(400).send({ msg: e.message });
}
}
I don't know how to assign the value of ObjectId to the user property, because ObjectId is generate when the doc is created, thus, I can not do this: userSettings.user = userSettings._id, because the objectr is already. I only manage to get something like this created:
{
"_id": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
But it should be:
{
"user": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
_id is the only mandatory property of a document. It is unique identifier of the document and you cannot remove it.
If you provide document without _id the driver will generate one.
You can generate ObjectId as
let id = mongoose.Types.ObjectId();
and assign it to as many properties as you want.
You can even generate multiple different ObjectIds for different properties of the same document.
Now, you don't really need to assign ObjectId to "user" property. _id will do just fine. In fact it is most likely you don't need user's settings in separate collection and especially as multiple documents with single key-value pair.
You should be good by embedding "settings" property to your "user" collection as a key-value json.

Check if a new field was added in a specific document like documentChanges for a collection in Firestore

I use this code for load comments in a table view:
func observePostComments(postId: String, completion: #escaping (String) -> Void) {
let db = Firestore.firestore()
db.collection("post-comments").document(postId).addSnapshotListener { (snapshot, err) in
if snapshot!.exists {
for key in (snapshot?.data()!.keys)! {
completion(key)
}
} else {
return
}
}
}
It works like it should, but every time a user creates a new comment, all comments are added again. I know how it works for a collection with:
querySnapshot?.documentChanges.forEach { diff in
if (diff.type == .added) { ....
But I can not figure out how to implement that functionality on a document / field level. If I want to do the same on a document level, I receive
Value of type 'DocumentSnapshot?' has no member 'documentChanges'.
How can I track changes on a specific document level, when a new Key-Value pair was added to a document?
Firestore's change detection only works on complete documents. If you need to know what changed inside a document, you will have to detect this in your own code, for example by comparing the previous DocumentSnapshot with the new one.
The exact way to do this depends a bit on what data you store, but there are two broad approaches:
You take something that is unique about each comment, and check if that's already present in your UI. This can for example be the ID of each comment, but anything else that's unique works too.
You store a timestamp for each comment, and keep track of the most recent timestamp you've already processed. Then in an update, you skip all comments up until that timestamp.
Another approach would be to clear the UI before adding the same comments to it. So something like:
db.collection("post-comments").document(postId).addSnapshotListener { (snapshot, err) in
if snapshot!.exists {
clearCommentsFromUI() // this is a function you will have to implement
for key in (snapshot?.data()!.keys)! {
completion(key)
}

How to query nested data in Firebase Database?

How can I make a query to get all bookings of all users that have timestamp in the range of 1519912278 ...1520689878 ? In Firebase docs it states that I can query deeply nested children.
According to this answer, it should work https://stackoverflow.com/a/47805607 , but it return null.
What is the flow of my User app? :
1. User singn up
2. When booking is made, it is saved at /Users/UID/stripe_customer_created_with_card/bookingNumber{booking object}
Why do I need all bookings located under /Users?:
There is a second app designed for Workers which can observe /Users node and each worker should be able to get bookings from /Users based on a timeStamp. For example a worker1382 can read bookings made by /Users from 16 March 2018 to 24 March 2018.
dbRef = FIRDatabase.database().reference().child("Users")
dbRef.queryOrdered(byChild: "TimeStampDateAndTime")
.queryStarting(atValue: "1519912278")
.queryEnding(atValue: "1520689878")
.observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
print(snapshot.value) //returns null
}
Users
UID
cus_CRC50bSq7rXuHv //stripe customer id for the user
617643762 //booking number
TimeStampDateAndTime: "1521309610"
Postcode: "E14 9DS"
BookingNumber:"617643762"
Another question: The parent key of the booking object is equal to BookingNumber. If I replace the parent key 617643762 with TimeStampDateAndTime value 1521309610, would I be able to order the bookings by time stamp and ultimately get only the ones that are within a certain range 1519912278 ...1520689878? So, the path would look like Users/UID/cus/1521309610: {object goes here}
From Firebase Documentation
Queries can also be ordered by deeply nested children, rather than only children one level down. This is useful if you have deeply nested data like this:
{
"lambeosaurus": {
"dimensions": {
"height" : 2.1,
"length" : 12.5,
"weight": 5000
}
},
}
To query the height now, we use the full path to the object rather than a single key.
Now, my question is, what can't I do if I don't know the full path?
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs")
ref.queryOrderedByChild("dimensions/height").observeEventType(.ChildAdded, withBlock: { snapshot in
if let height = snapshot.value["height"] as? Double {
println("\(snapshot.key) was \(height) meters tall")
}
})
EDIT 2
Initial Structure looks like this:
{
"Users" : {
"I50dHN7wPqPJClPfYDG76gXizdD2" : { //-> user?.uid of a logged in user
"cus_CRC50bSq7rXuHv" : {
"617643762" : {
"TimeStampDateAndTime" : "1521309610"
}
}
}
}
}
Modified structure:
{
"Users" : {
"I50dHN7wPqPJClPfYDG76gXizdD2" : {
"1521309610" : { // -> store timeStamp as the immediate child key of UID rather than the booking number
"TimeStampDateAndTime" : "1521309610",
"BookingNumber": "617643762"
}
}
}
}
{
"UsersProfile":{
"I50dHN7wPqPJClPfYDG76gXizdD2":{
"StripeCustomer":"cus_CRC50bSq7rXuHv", // -> store StripeCustomer on the UsersProfile
"Name": "John Doe"
}
}
}
You can query properties at a known path under each child node. So you can query dimensions/weight in the dinosaur sample, and you can query 617643762/TimeStampDateAndTime from your data.
But you can't query */TimeStampDateAndTime, since that's not a known path.
If you need to get a list of bookings in a date range across all users, you will need to modify your data structure to allow that query. For example, by adding a top-level list of bookings:
"Bookings": {
"book_617643762"
Customer: "cus_CRC50bSq7rXuHv"
TimeStampDateAndTime: "1521309610"
Postcode: "E14 9DS"
}
With this additional structure, you can then query:
dbRef = FIRDatabase.database().reference().child("Bookings")
dbRef.queryOrdered(byChild: "TimeStampDateAndTime")
.queryStarting(atValue: "1519912278")
.queryEnding(atValue: "1520689878")
.observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
print(snapshot.value) //returns null
}

How do I retrieve a key from a firestore document (using swift 4/ios)

In the firebase docs for firebase database there is info on flattening a database and a message app is used as an example.
https://firebase.google.com/docs/database/web/structure-data
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
Under "members"/"one" each key is the name of a participating member in the chat. I'm pretty sure in firebase database there is a getKey method to get each key.
I have set up my database (using firestore instead of firebase) in a similar way but where each chat is a unique identifier and the document contains the members of the chat where the key is their firebase auth id eg.
var docsref: DocumentReference?
docsref = self.defaultStore?.colleection("chats").document(chatID)
docsref?.getDocument { (document, error) in
if let _ = document {
print("document!.data()")
} else {
print("Document does not exist")
}
}
And when I output:
print(document!.data())
I get ["2mHuccccxxxxxxxxxxk4wkIAt33": 1] which is the firebase auth code of one of the participating chat members and a boolean.
I would like to get that key but wasn't sure how.
Thanks.
I'm not sure if it's the exact Syntax but self.defaultStore?.colleection might be a typo with the double E. It could also possibly be that you're missing a step. You're getting inside the chatID and you're expecting to output the keys inside members without actually accessing it. I'd access members after .document(chatID)
You can get it as follows.
to get value document.data()
to get key document.documentID

Structuring data for chat app in Firebase

Im following Firebase guide to structuring data for a chat app. They suggest the structure as seen below.
{
// Chats contains only meta info about each conversation
// stored under the chats's unique ID
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// Messages are separate from data we may want to iterate quickly
// but still easily paginated and queried, and organized by chat
// converation ID
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}
How do I structure my user data so that I can easily display a list of all of the chats they are part of and for each one of them display the last message and timestamp. If I do the following structure:
"users": {
"ghopper": {
"name": "Gary Hopper",
"chats": {
"one: true",
"two": true
}
},
"alovelace" { ... }
},
I can easily get a list of each chat group for a specific user, for example ghopper, by doing (in swift):
ref.child("users").child("ghopper").child("chats").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//do something with data
}
However I won't have the lastMessage and timestamp in this snapshot. What do I need to do to access this data?
Duplicate all this data for each user? i.e adding users/ghopper/chats/one/ {"lastMessage": "ghopper: Relay malfunction found. Cause: moth.", "timestamp" : 1459361875666}
Make a query for "chats/specificGroupId" for each chat that the user is part of (adding multiple listners)?
Some other way?
How do I structure my user data so that I can easily display a list of
all of the chats they are part of and for each one of them display the
last message and timestamp.
Change the chats structure a tad by adding users who are in the chat node
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
users
uid_1: true
uid_3: true
},
"two": { ... },
Then you can deep query for all chats a particular user is part of - this will return the chats uid_3 is involved in
chatsRef.queryOrderedByChild("users/uid_3").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
//.Value can return multiple nodes within the snapshot so iterate over them
for child in snapshot.children {
let lastmsg = child.value["lastMessage"] as! String
let timestamp = child.value["timestamp"] as! String
print(lastmsg)
print(timestamp)
}
})
Note that each firebase user has a discreet user id obtained when the user is created via auth.uid. This should (generally) be used as the key for each user.
In the block where you have a list of all the chats a user is in, can you do:
var dictionary: [String: Long]
var lastMessage: String
for chat in listOfChatsUserIsIn
ref.child("chats").child("\(chat)").child("lastMessage").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
lastMessage = snapshot
ref.child("chats").child("\(chat)").child("timestamp").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//add timestamp and last message to dictionary
}
}
I don't know how correct my syntax is. Still learning firebase. But, I think this is basically your second suggestion. Don't know how else you would get the data. This would be O(2n) though which isn't bad.
[[Update 1]]
I was being lazy with my code. I put lastMessage = snapshot to save it so you could add it to the dictionary in the next block.
As for Firebase being asynchronous. I think this would still work as long as you use either the timestamp or message as the key and the other as the value in the dictionary. It may be populated out of order, but you could always sort it by timestamp later. Although, yes, this probably is not best practice.
Jay, I like your solution. Wouldn't you have to also list uid_2: false?
Unfortunately, it seems like both these database structures grow by n^2 as users -> inf and as chats -> inf.