I'm building a chat app with rooms feature in iOS, and had built a Firebase data design like this:
"members" : {
"userId1" : {
"roomId1" : true
},
"userId2" : {
"roomId1" : true
}
}
"rooms" : {
"roomId1" : {
"lastMessage" : "Last message",
"timeStamp" : 1494483604,
//users in this room
"users" : {
"userId1" : true,
"usreId2" : true
}
}
}
So to show list of conversations of a user, firstly I observe single event of type value of path members/userId to get list of rooms that user take part in.
Then for each roomId, I observe rooms/roomId to get data to show on the UI.
The question is if a user takes part in a great deal of rooms, is observing changes for all of them best practice ?
For example, if I have 30 conversations from roomId1 to roomId30, I want to update the latest messages on the UI whenever changes happened, is observing 30 references makes sense ?
Thank you.
It's not best practice but it's a practice that works. However, based on your structure it would be simpler to generate a query on the rooms node for any users/userIdx: true.
That will add and observer to one node and notify the app of any changes to rooms the user is part of.
For example
Given a structure
rooms
room_0
room_name: "My Room"
users:
uid_0: true
uid_1: true
uid_2: true
room_1
room_name: "Romper Room"
users:
uid_0: true
uid_2: true
and some code to add an observer to watch for uid_1
let roomsRef = self.ref.child("rooms")
roomsRef.queryOrdered(byChild: "users/uid_1").queryEqual(toValue: true)
.observe(.childAdded, with: { (snapshot) in
let roomDict = snapshot.value as! [String: AnyObject]
let roomName = roomDict["room_name"] as! String
print(roomName)
})
When this code is first run, it will print out
My Room
because the user is part of My Room (room_0) and not part of room_1.
If you then add uid_1: true to room_1 it will print
Romper Room
You should use observe to update db changes, so you can update UI based on the observation.
Take a look at this document:
https://firebase.google.com/docs/database/ios/read-and-write
Related
I am working on a project developing a car rental app. The app can be accessed by admin and user both with different roles. So at the moment i'm saving the car added by the admin using api / manually in firebase
let vehicle = Vehicle(make: make, model: modell, price: price, mileage: mileage)
db.collection("Admin").document("car").setData(from: vehicle)
where vehicle is an object with properties like make model mileage ect. Now with this funtionality i always save one object. If i add a new object it overwrites it how can i store a multiple objects in car document.
The reason why it is "updating" instead of "adding" when you are running above statements for second time because you are using the same DocumentID, which you specify as "car".
If you are running a for-loop function to add multiple vehicle documents, you can modify your codes similar to below example.
Method 1
let vehicleDocID: Int = 0
let vehicle = Vehicle(make: make, model: modell, price: price, mileage: mileage)
for vehicle in vehicleS {
vehicleDocID += 1
db.collection("Admin").document("\(vehicleDocID)").setData(from: vehicle)
}
Alternatively, you can simply use .add() function to get defaultID, which I personally recommend.
Method 2
var ref: DocumentReference? = nil
ref = db.collection("Admin").addDocument(data: [
"make": "make",
"model": "modell",
"price": "price",
"mileage": "mileage"
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
Lastly, in case, if the number of vehicles are large (>100), I suggest you use batch writes to add the documents to Firestore, as it ensures better performance. For batch writes, you may refer to below Firestore documentation.
https://firebase.google.com/docs/firestore/manage-data/transactions
I'm trying to create a chat app with groups. Players can join multiple groups. The problem that I'm facing is that I don't know how to add multiple groups to a player. If the player is joined in 1 group I could use 'Group' as key and the group name as value but with multiple groups this isn't possible. I can't create multiple 'Group' keys because then they aren't unique. Maybe an array as value for 'Group'? But I don't think this is possible. I hope someone can help me. Thanks!
You can use an array, but it's better to use an object with firebase-generated keys. See the proposed database structure below:
"root": {
"users": {
"$userId": {
...
"groups": {
"$groupId": true
}
}
},
"groups": {
"$groupId": {
...
}
}
}
I haven't coded for iOS, so the following example is in JavaScript. With the database structure above, you can add user to groups and read which groups the user is in like this:
var currentUserId = ?; // The userId of the logged in user
var currentUserGroupsRef = rootRef.child('user').child(currentUserId).child('groups');
// Join group
var groupId = ?; // The id of a group that the user joins
currentUserGroupsRef.child(groupId).set(true); // Add the groupId to the users groups, letting firebase generate an Id
// Read groups
currentUserGroupsRef.on('value', (snap) => {
if (!snap.val()) {
var userGroupIds = []; // user is not in any groups, empty array of groupIds
return;
}
var userGroupIds = Object.keys(snap.val()); // Array of groupIds
});
In my React-Native app, I have an array of specific users whose values I want to pull from Firebase. What is the most efficient way to go about this? Currently I am looping through the array and making a new request for each (relevant code below):
const usersRef = new Firebase(`${ config.FIREBASE_ROOT }/users`)
for (var key in usersArray) {
var userRef = usersRef.child(key);
//do stuff here
}
However, I feel this isn't very efficient and it makes several requests to the database. Is there a way I can pass in the array and get those items from Firebase, all in one call? Thanks.
Firebase data structure:
{
"items" : [ {
"description" : "fuzzy socks",
"type" : "toy"
}, {
"description" : "bouncy ball",
"type" : "toy"
}, {
"description" : "scrabble",
"type" : "game"
}, {
"description" : "construction paper",
"type" : "crafts"
} ],
"users" : [ {
"itemList" : [ 1, 2, 3, 4 ],
"description" : "brown",
}, {
"itemList" : [ 5, 6, 7 ],
"description" : "green",
}, {
"itemList" : [ 8, 9, 10 ],
"description" : "blue",
}, {
"itemList" : [ 11, 12, 13 ],
"description" : "yellow",
} ]
}
Simplified use case: In one use case, I only want to get information about 2 of the users (out of all the users I have stored in Firebase--assume it's many more than just the 4 in the structure above). So, I have the array importantUsers:
var importantUsers = [0, 3]
Then, I want to send a request to Firebase that only queries the database for the values associated with these userID values (so somehow pass in the array to Firebase for a result). Return values would be something like this:
0: itemList: [1,2,3,4], description: brown
3: itemList: [11,12,13], description: yellow
My motivation for querying the database for multiple users at once (rather than creating a separate ref for user 0 and user 3) is to not have multiple calls made to Firebase. Is there any way to go about this?
So what you are after is an sql 'in' type query. Select in [0,2]. To select a number of users from a list.
The additional challenge in your question is that users you are interested in are random so you can't use .startAt and .endAt, and there is no other relation between the users.
Firebase does not have direct support for 'in', 'and' or 'or' kinds of query but there are a number of ways to make it happen.
How about this: flag the users you want and then with a single query, read them in.
First, you'll start with a typical Firebase /users node with the addition of a 'selected' child node (this can be omitted initially but I am showing it here as a placeholder)
users
uid_0
name: "Bud"
selected: false
uid_1
name: "Henry"
selected: false
uid_2
name: "Billy"
selected: false
Then, we need some random uid's, say uid_0 and uid_2 and store those in an array. Keep in mind that we would be using the Firebase generated uid but we'll use uid_0, uid_1 etc for simplicity.
With just two users, you could just observeSingleEventOfType on each of the two nodes, no big deal.
However, if we needed 100 random users or 1000, doing 1000 separate queries or observeSingleEvent's should be avoided. But, setValue is blisteringly fast (no returned data) so....
Get our users ref
let usersRef = myRootRef.ChildbyAppendingPath("users")
We know the path to each of the 100 users we want by iterating over the array to build those refs and set selected to true
for uid in uidArray {
let thisUserRef = usersRef.childByAppendingPath(uid)
let selectedRef = thisUserRef.childByAppendingPath("selected")
selectedRef.setValue(true)
}
Then, you can query for all users in the usersRef where the selected child = true.
Once you have them, to clean up, iterate over the returned users and set the selected to false or nil
The cool thing about this is that setValue can blast through 100 or 1000 users very quickly with no overhead, setting their selected child to true. Then a single query can return the values you want.
Seems almost reverse in logic to write out to then read back in but I am pretty sure it's considerably better than iterating over an array and generating 1000 queries or observers.
(Firebase folks can check me on that one)
One other thought is that if a user is say, clicking on other users in a list, you could set selected = true as they are clicking and then query for those when the user is done.
You could use bindAsArray or bindAsObject from ReactFire
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/items");
this.bindAsArray(ref, "items");
Then you could loop over that array/object as needed without extra queries
I'm currently trying to setup a friendslist relationship using Firebase and I'm experiencing some issues with the process of adding a friend for a user.
I currently have a schema like this:
users
-----
UID
->Name
->Friendslist
->UID of friend:true
userDisplays
------------
-Users Name:Users UID
addedFriends
------------
-UID of added user
->UID Of user that did the adding
My idea of adding a friend is using the name of the user and looking them up in the userDisplays tree, that lookup seems to work everytime. However, when I then try to read the uid value from the child in userDisplays it will sometimes return NULL, or will skip the adding entirely after it finds the uid value (by this I mean it will not add the uid to the adding user and not create a addedFriends child).
The same issue is when the adding user successfully adds the desired user and a addedFriends child is created linking the two, but again when the added user checks for recent addings and is given the option to 'add back' it will return NULL or skip the removal of the addedFriends child and not add the uid to the users list of friends.
I tried to copy the relationship example found in the docs, but I'm confused on where I am missing things, or if this is expected behavior?
Here is my structure after two users have been created. Before a friends list has been created.
{
"userDisplays" : {
"a" : "fed7738e-7dec-4ff3-9cc9-46dee3c189e6",
"t" : "c3b27833-d56b-40e9-b46f-c99128193f3d"
},
"users" : {
"c3b27833-d56b-40e9-b46f-c99128193f3d" : {
"displayName" : "t",
"email" : "test#test.com",
"provider" : "password"
},
"fed7738e-7dec-4ff3-9cc9-46dee3c189e6" : {
"displayName" : "a",
"email" : "test2#test.com",
"provider" : "password"
}
}
}
This is the swift code I'm using to perform the adding of a friend given a user name:
ref.userDisplayBaseSingleton.childByAppendingPath("/\(userName)").observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull{
print("null")
}else{
print(snapshot.value)
print("found val")
ref.userBaseSingleton.childByAppendingPath(snapshot.value as! String).observeSingleEventOfType(.Value, withBlock: { (snap) in
let friendData = [
snap.key:true
]
ref.userBaseSingleton.childByAppendingPath("\(currentUser!.id!)/friends").updateChildValues(friendData)
let currentAddData = [
snap.key:currentUser!.id!
]
let friendAddRef = ref.addBaseSingleton.childByAutoId()
friendAddRef.setValue(currentAddData)
})
}
})
Basically the issues I'm experiencing from this snippet is that sometimes the look up from userDisplays doesn't return a value, or if it does it doesn't add a addedFriends node. I've experienced a bug when retrying adding the username again, but it will actually generate a new uid of the users and then generate a new addedFriends child.
For example, if I was user t and I tried to add a a few kinds of behavior can happen.
userDisplay for a returns null
userDisplay returns a and returns a's uid but the addedFriends node isn't generated
a is properly added, and an addedFriends node is generated, however when user a is notified of the add and is given the option to add back t it seems to reproduce the same kind of errors.
I've noticed that after a period of time, the actual uid of t will update on the firebase and then the add will be successful. By this I mean that the initial uid that t is assigned will change and then I've noticed the code above will work. I'm also assuming the reason why its updated has to do with the errors thats happening, but I could be wrong.
Heres a snippet of the error happening
}
{
"addedFriends" : {
"-KH6pR5TV7vjVeADc7mc" : {
"22a3c5fa-e578-4ada-b3c1-355add80041e" : "098804cd-1ffa-4508-b353-6f6a4377661f"
}
},
"userDisplays" : {
"test" : "f0bb77a9-8156-4d44-ad74-6d6b01765802",
"test2" : "098804cd-1ffa-4508-b353-6f6a4377661f"
},
"users" : {
"098804cd-1ffa-4508-b353-6f6a4377661f" : {
"displayName" : "test2",
"email" : "test2#test.com",
"friends" : {
"22a3c5fa-e578-4ada-b3c1-355add80041e" : true
},
"provider" : "password"
},
"f0bb77a9-8156-4d44-ad74-6d6b01765802" : {
"displayName" : "test",
"email" : "test#test.com",
"provider" : "password"
}
}
}
I am trying to make a query to a Firebase collection using swift.
My Collection is like this :
{
"networks" : {
"-KF9zQYGA1r_JiFam8gd" : {
"category" : "friends",
"members" : {
"facebook:10154023600052295" : true
},
"name" : "My friends",
"owner" : "facebook:10154023600052295",
"picture" : "https://my.img/img.png"
},
"-KF9zQYZX6p_ra34DTGh" : {
"category" : "friends",
"members" : {
"tototata" : true
},
"name" : "My friends2",
"owner" : "facebook:10154023600052295",
"picture" : "https://my.img/img.png"
}
}
and my query is like that :
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members")
.queryEqualToValue(true, childKey: uid)
Then I am populating a TableView using FirebaseUI.
the thing is that query doesn't return anything, I also tried using this kind of query :
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members")
.queryStartingAtValue(true, childKey: uid)
.queryEndingAtValue(true, childKey: uid)
And to be clear "uid" is the node name nested in the "members" node with true as value. (As on the picture)
I can't get all the nodes without filtering because of the amount of data to be downloaded.
I'm stuck on that for a day, and I'm going crazy.. :-)
If someone can help ?
The correct syntax would be:
let networksRef = ref.childByAppendingPath("networks")
.queryOrderedByChild("members/\(uid)")
.queryEqualToValue(true)
But note that you'll need an index for each uid for this to work efficiently:
{
"rules": {
"networks": {
".indexOn": ["facebook:10154023600052295", "tototata"]
}
}
}
Without these indexes, the Firebase client will download all data and do the filtering locally. The result will be the same, but it'll consume a lot of unneeded bandwidth.
A proper solution for this requires that you change your data structure around. In Firebase (and most other NoSQL databases) you often end up modeling the data in the way that your application wants to access it. Since you are looking for the networks that a user is part of, you should store the networks per user:
"networks_per_uid": {
"facebook:10154023600052295": {
"-KF9zQYGA1r_JiFam8gd": true
},
"tototata": {
"-KF9zQYZX6p_ra34DTGh": true
},
}
Adding this so-called index to your data structure is called denormalizing and is introduces in this blog post, the documentation on data structuring and this great article about NoSQL data modeling.