Firebase fanout data to update specific fields remove other siblings fields - ios

Each user has a conversation node, each time a new conversation has a new message I need to update both conversation nodes for the two user involved in the conversation, I want just to update the "lastMessage" and "tinestamp" fields here is my try:
let fanoutObject = [userPath : dataToUpdate,
otherUserPath : dataToUpdate]
K.FirebaseRef.root.updateChildValues(fanoutObject)
where the paths for each user is:
"/users/{userID}/conversations/{conversationID}"
and the dataToUpdate:
let dataToUpdate:[String:AnyObject] = ["timestamp" : message.timestamp,
"lastMessage": message.textBody]
Result:
The node conversations for each user is updated BUT other fields in the conversation node are removed !
the conversation node fro each user is:
"conversations" : {
"{conversationID}" : {
"lastMessage" : "your name ?",
"seen" : true,
"timestamp" : 1467849600000,
"with" : {
"country" : "US",
"firstName" : "John",
"profileImage" : "https://..."
}
}
}
note that the node conversations is inside a node user which is an element inside the root node users
and after update it's :
"conversations" : {
"{conversationID}" : {
"lastMessage" : "your name ?",
"timestamp" : 1467849600000,
}
}
but I was expecting just to update the two values and keep others ?
According to docs my code should works:
updateChildValues Update some of the keys for a defined path without
replacing all of the data.

It's a bit hard to parse your code, but most likely it's the behavior of updateChildValues() that is confusing you.
When you call updateChildValues(), the Firebase server will loop over the object that you pass in. For each path in there, it will replace the entire value at that path with the value from that you passed in.
So if your current JSON is:
{
"Users": {
"uidForUser1": {
"name": "iOSGeek",
"id": 2305342
},
"uidForUser2": {
"name": "Frank van Puffelen",
"id": 209103
}
}
And the update is (in JSON format, the lingua franca of the Firebase Database):
{
"users/uidForUser2/name": "puf",
"users/uidForUser1/name": "My actual name"
}
Your resultant JSON will be:
{
"Users": {
"uidForUser1": {
"name": "My actual name",
"id": 2305342
},
"uidForUser2": {
"name": "puf",
"id": 209103
}
}
But if you send the following update:
{
"users/uidForUser1": {
"name": "My actual name"
},
"users/uidForUser2": {
"name": "puf"
}
}
The resulting JSON will be:
{
"Users": {
"uidForUser1": {
"name": "My actual name"
},
"uidForUser2": {
"name": "puf"
}
}
Update
To update two fields in the same object, but leave the other fields unmodified:
{
"path/to/object/field1": "new value",
"path/to/object/field2": "new value2"
}

Alternatively, you can update the lastMessage and timeStamp data by replacing the old values by providing full path :
let lastMessagePath = "/users/{userID}/conversations/{conversationID}/lastMessage"
let lastTimeStampPath = "/users/{userID}/conversations/{conversationID}/timestamp"
K.FirebaseRef.child(lastMessagePath).setValue(message.timestamp)
K.FirebaseRef.child(lastTimeStampPath).setValue(message.textBody)

Related

How can I limit Firebase Realtime db items to "users" who exist in an object

I have a realtime db all setup and working. The data structure is very simple:
Item
some: info
some: other info
Item 2
some: info
some: other info
My rules are also super simple:
{
"rules": {
".read":"auth.uid != null",
".write":"auth.uid != null"
}
}
The issue (obviously) is that while I am forcing a user to be authenticated, that's all I am requiring and any user can access all the items in the db.
What I want is a way to limit a user to an item.
something like:
Item1
some: info
some: other info
user_1: auth.uid
user_2: auth.uid2
Item2
some: info
some: other info
user_1: auth.uid3
user_2: auth.uid4
I can store that data but I am not sure how to structure my rules to limit that.
My actual json looks like:
{
"annotations": {
"8df0309f-dc62-821e-dd65-f0ad46396937": {
"author": "1OXVKN3Y5Z-11",
"xfdf": "LONG STRING"
}
},
"complete": false,
"created_at": "2020-09-01T17:52:25.653Z",
"field_values": {
"field_name": {
"name": "copy",
"value": "TEsting",
"widgetID": "e61e3abf-7cdd-7d07-daec-6c3d3a55d667"
}
},
"stamp_count": 0
}
What I plan to implement is:
{
"annotations": {
"8df0309f-dc62-821e-dd65-f0ad46396937": {
"author": "1OXVKN3Y5Z-11",
"xfdf": "LONG STRING"
}
},
"complete": false,
"created_at": "2020-09-01T17:52:25.653Z",
"field_values": {
"field_name": {
"name": "copy",
"value": "TEsting",
"widgetID": "e61e3abf-7cdd-7d07-daec-6c3d3a55d667"
}
},
"stamp_count": 0,
"users": [ "CFX4I0PTM9-11", "CFX4I0PTM9-7"]
}
One I implement that json structure, how can I setup rules to support?
From reading your question and the comment thread I think your requirement is:
Allow a user to access an item if their UID is associated with that item.
In that case, you'll first need to ensure that the UIDs are in keys, as you can't search across multiple values, as your proposed users array would require. So you'd end up with:
"items": {
"item1": {
...
"users": {
"CFX4I0PTM9-11": true,
"CFX4I0PTM9-7": true
}
}
}
Now with this structure, you can ensure a user can only update items where their UID is in the users map with rules like this:
{
"rules": {
"items": {
"$itemid": {
".write": "data.child('users').child(auth.uid).exists()"
}
}
}
}
For reading the specific item you could use a similar rule. That will allow the user to read an item once they know its complete path, and when their UID is in the users map.
But you won't be able to query this structure, as you can only index on named properties. For more on this, and the alternative data structure to still implement you use-case, see Firebase query if child of child contains a value

Query object in child array

I am trying to use a query to retrieve data of student who are only 4 years old from my database but i cant figure out how to use Firebase to query the age (The array in each child).
{
"student" : {
"-Kv2RVDDsI-v6V7g_LBn" : [ {
"name" : "sam",
"age" : "6"
}, {
"name" : "tom",
"age" : "4"
}
],
"-hguyu-v6V7g_LBn" : [ {
"name" : "Tim",
"age" : "12"
}, {
"name" : "tom",
"age" : "4"
}
]
}
}
This is my code but it does no return anything.
ref.child("student").queryOrdered(byChild: "age").queryEqual(toValue: 4).observe(.childAdded, with: { (snapshot) in
let value = snapshot.value
print(value)
}) { (error) in
print(error.localizedDescription)
}
However, if i remove the queryOrdered(byChild: "age") it works.
Thanks.
You are referencing "Student" when actually your JSON database format child path name is "Students".
Also make sure that you are querying by key. Refer to the Firebase documentation to see how to query by key. I don't understand your structure though why you have 2 children per key.
Why not have a new key for each child?

Firebase ios: get list of users by IDs

Situation: I have a database with users, users can have friends and friends are linked to current user model as userID. So when I'm loading user data I'm receiving also a list of firebase IDs of his friends.
Question: Is there any way to receive firebase snapshot with all this users at once? Haven't found any suitable solution - .child() is linking directly with one object, queryOrderedByChild() and queryOrderedByValue() doesn't seem to make such requests.
Will be grateful for any advice.
Edit: Database structure:
{
"Users": {
"user_id_first" : {
"user_info" : {
"name": "name",
"age": "age"
},
"friends": {}
},
"user_id_second" : {
"user_info" : {
"name": "name",
"age": "age"
},
"friends": {}
},
"user_id_third" : {
"user_info" : {
"name": "name",
"age": "age"
},
"friends": {
"user_id_first" : true,
"user_id_second" : true
}
}
}
}
There are list of friend IDs, which are actually IDs of firebase users. All I need is to retrieve information for these users in one firebase snapshot (i.e. use one reference) without creating new reference for each friend like
myDBRef.child("Users").child("friend_id")
Use for loop to access all the friendsId :-
let parentRef = FIRDatabase.database().reference().child("Users")
parentRef.child(FIRAuth.auth()!.currentUser!.uid).child("friends").observeEventType(.Value, withBlock: {(snapshot) in
if snapshot.exists(){
if let friendsDictionary = snapshot.Value as! NSMutableDictionary{
for each in friendsDictionary as! [String : AnyObject]{
let friendsId = each.0 as! String
parentRef.child(friendId).observeEventType(.Value, withBlock: {(friendDictionary)
if let friendsInfo = friendDictionary.Value as! NSMutableDictionary{
//retrieve the info
}
})
}
}
}
})

Can Geofire query location if it's a child key of an autoID?

According to GeoFire, it allows you to query all keys within a geographic area. If that's the case, can I query for object locations if location is a child key of an autoID?
Basically, I want to query for all messages within a geographic radius, and then sort them by recency. Is autoID the best key to use for identifying many objects? Let's say I have 100 messages in a defined radius, how would I identify them and subsequently retrieve them?
{
"Messages" : {
"-KFGX5H3rLSnpPvupakm" : {
"message" : "Concert at the park!",
"location" : {
"g" : "xxxx",
"l" : {
"0": "xxx",
"1": "xxx"
}
},
},
"-KFGZkwAIl817CLDLmMp" : {
"message" : "It's gonna be a foggy one.",
"location" : {
"g" : "xxxx",
"l" : {
"0": "xxx",
"1": "xxx"
}
},
}
}
}

Firebase queryOrderedByChild not ordering by name in Swift

I have in the security & rules option this:
{
"rules": {
".read": true,
".write": true,
"groups": {
".indexOn": "name"
}
}
}
And my JSON structure is like this:
{
"groups": {
"-KAti17inT7GbHEgbzzS": {
"author": "ruben",
"name": "C"
},
"-KAti36BQGO8sRmfufkE": {
"author": "ruben",
"name": "D"
},
"-KAti2CVAHtJllQm_m-W": {
"author": "ruben",
"name": "A"
}
}
As you can see, it is not ordered by "name", as is it supposed to be.
What I'm doing wrong?
That .indexOn instruction in the security rules only tells the database to create an index on the name property. It doesn't automatically re-order the data.
In addition: the order of keys in a JSON object is undefined.
To get a list of your groups by name, you have to execute a Firebase query. From the Firebase documentation on queries:
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs")
ref.queryOrderedByChild("height").observeEventType(.ChildAdded, withBlock: { snapshot in
if let height = snapshot.value["height"] as? Double {
println("\(snapshot.key) was \(height) meters tall")
}
})
If you adapt this snippet to your data structure, it will print the groups in the correct order.

Resources