I'm novice with firebase and I try to structure my data to store friendship between users of my app.
My data are structured like this :
{ users: {
user_1: {
name: John
},
user_2: {
name: Jack
},
user_3: {
name: Georges
},
},
{ friendships : {
user_1 : {
user_2: {
name: Jack
},
user_3: {
name: Georges
},
user_3: {
user_1: {
name: John
},
user_2: {
name: Jack
}
}
}
I don't know if my data structure is OK, but I wonder what is the best way to update a name in the friendships if a user changes his name, how can I easily update the reference in the friendships (brows each node ?) ?
Thank you if someone can help me to understand the right way to do that.
A.
The database structure that you are following is a bit messy and might prove a bit hard to navigate through it, Might i suggest :-
Users{
userID_1 : {
username : “Shelly Morgan” ,
useremail : “sehlly1#yahoo.co.in”
friends :{
userID_2 : true,
userID_3 : true,
userID_4 : true,
}
},
userID_2 : {
username : “Mikael” ,
useremail : “mike1232#gmail.com”
friends :{
userID_1 : true,
userID_4 : true,
}
},
userID_3 : {
username : “Lenoard Narish” ,
useremail : “leo_nar12#gmail.com”
friends :{
userID_1 : true,
userID_2 : true,
userID_4 : true,
}
},
userID_4 : {
username : “Rob Stark” ,
useremail : “robStar781#gmail.com”
friends :{
userID_1 : true
}
}
}
In this manner you only store the userID of friends in that users database, and all you have to do is change the values in only friends_uid node.To retrieve the friends database:-
1.) just hit the friends node of that User
2.) listen for every friends uid,
3.) and hit the database with the desired uid to retrieve database of respective friend
Read the documentation in here for retrieving the data : https://firebase.google.com/docs/database/android/retrieve-data
Your structure is almost good, but I would change the friendships node to this:
{ users: {
user_1: {
name: John
},
user_2: {
name: Jack
},
user_3: {
name: Georges
},
},
{ friendships : {
user_1 : {
user_2: true,
user_3: true,
user_3: {
user_1: true,
user_2: true
}
}
This is similar to what Dravidian showed, but depending on how many friends you have, it might be a better idea to not store the users friendslist under the users account details node because if you had 1000+ friends you would have to download 1000 children even if you just wanted to get a single value like his name.
You shouldn't store name in the friendships structure, only the usernames. So friendships should look more like this:
{
friendships: {
user_1: [
user_2,
user_3
],
user_2: [
user_1
]
}
}
This way if you update name, you only have update it in one place (users). You can cross-reference the username from friendships with the username in users to find name.
Also in case you use push() for friendships, the structure will change a little, but the same principle applies:
{
friendships: {
user_1: {
-KPE008BeSzNR5D3W7t3: user_2,
-KPE0nF4AW7Lj66xTLUu: user_3
},
...
}
}
Related
I'm trying to create admins for an app by manually entering them in Firebase based on their Twitter login generated uid. I'm wondering how I can search all of the children under a node for a specific uid to specify r/w access.
I have the following
{
"GRP1" : {
"ORG1" : {
"CONF1" : {
"TEAM1" : {
"ADMINS" : {
"Name1" : {
"uid" : "abcd1239"
},
"Name2" : {
"uid" : "abcf1234"
}
},
"FEED" : {
"Store1" : {
"info1" : "some text"
},
"Store2" : {
"info2" : "some other text"
}
}
}
}
}
}
}
I want to provide r/w access to FEED based on if the uid exists in ADMINS. This rules works:
".read" : "root.child('GRP1').child('ORG1').child('CONF1').child('TEAM1').child('ADMINS').child('Name1').child('uid').val() === auth.uid"
But if I have variable number of ADMINS, how do I search through all of the 'uid'? I have tried entering the child name as the uid, then searching to see if it exists but the read would not work:
".read" : "root.child('GRP1').child('ORG1').child('CONF1').child('TEAM1').child('ADMINS').child(auth.uid).exists()"
You can't search across nodes in security rules, as that would not scale. This means that if you want to check whether the UID exists in a certain place in your JSON, you must have that UID as the key of the node.
So your JSON would be:
{
"GRP1" : {
"ORG1" : {
"CONF1" : {
"TEAM1" : {
"ADMINUIDS" : {
"abcd1239": "Name1",
"abcf1234": "Name2"
}
Now you can check for the existence of the UID with:
".read" : "root.child('GRP1/ORG1/CONF1/TEAM1/ADMINUIDS').child(auth.uid).exists()"
I have the following Firebase database structure. uIds is a type of List<String>. I am trying to add another uId under uIds with an incremented index. setValue() and updateChildren() would require me to retrieve existing data, and push() will add an item with a randomly generated string as a key instead of an incremented index. Is there a simpler way that does not require to retrieve the existing data? Thanks!
"requests" : {
"request001" : {
"interests" : [ "x" ],
"live" : true,
"uIds" : [ "user1" ] // <---- from this
},
"request002" : {
"interests" : [ "y" ],
"live" : true,
"uIds" : [ "user2" ]
}
}
--------------------------------
Edit:
Sorry for the unclarity. Let me elaborate to make it clear.
Say I have the above database and want to update it to the following.
"requests" : {
"-KSVYZwUQPfyosiyRVdr" : {
"interests" : [ "x" ],
"live" : true,
"uIds" : [ "user1", "user2" ] // <--- to this
},
"-KSl1L60g0tW5voyv0VU" : {
"interests" : [ "y" ],
"live" : true,
"uIds" : [ "user2" ]
}
}
ishmaelMakitla's suggestion, mDatabase.child("requests").child("request001").setValue(newRequest), will overwrite the "request001" with "newRequest". So I should retrieve the existing data of "request001" and add "user2" to the list uIds. It will be something like this:
mDatabase.child("requests").child("request001").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Request newRequest = dataSnapshot.getValue(Request.class);
newRequest.uIds.add("user2");
mDatabase.child("requests").child("request001").setValue(newRequest);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
But I am wondering if this process is necessary since what I am trying to do is simply to add one item to the list uIds.
The Firebase documentation on creating data that scales proposes that you use a different data structure:
"requests" : {
"-KSVYZwUQPfyosiyRVdr" : {
"interests" : { "x": true },
"live" : true,
"uIds" : {
"user1": true,
"user2": true
}
},
"-KSl1L60g0tW5voyv0VU" : {
"interests" : { "y": true },
"live" : true,
"uIds" : {
"user2": true
}
}
}
Here are a few of the reasons why this data structure works better:
each uid can now automatically only appear once. We've essentially modeled our data as a set, instead of using an array.
adding an item is now as easy as ref.child("uUids").child("user3").setValue(true)
you can now check if a uid exists in your security rules.
I have started re-iterating to myself: whenever you find yourself doing array.contains("xyz"), you should probably be using a set instead of an array. The above mapping with "key": true is an implementation of a set on Firebase.
Efficiency
Some people may think arrays are a more efficient way of storing the data, but in the case of Firebase that is not true:
What you see:
"uIds" : [ "user1", "user2" ]
What Firebase stores:
"uIds" : {
"0": "user1",
"1": "user2"
}
So storing a set is pretty much the same:
"uIds" : {
"user1": true,
"user2": true
}
Not sure what you mean when you say setValue, etc require you to retrieve existing data. The basic flow for inserting new record is as follows:
private DatabaseReference mDatabase;
// get reference to your Firebase Database.
mDatabase = FirebaseDatabase.getInstance().getReference();
//and here you add a new child to your 'requests' collection
//I am assuming you have a Request model like this..
Request newRequest = new Request(some-params);
mDatabase.child("requests").child(someRequestId).setValue(newRequest);
You can take a look at basic usage guide for Saving Data on Android Firebase.
Update:
Following your comment - I think what you are looking to do can be achieved like this:
You use the push() method which generates a unique ID every time a new child is added to the specified Firebase reference:
Firebase newRequestRef = mDatabase.child("request").push();
newRequestRef.setValue(newRequest);
This should do it.
I hope this helps.
There is a good old article from the Firebase official blog explaining why we should avoid array in our database : Arrays are Evil
So it's not possible to modify an array without replacing the array. I suggest to change your database structure to this
"requests" : {
"<pushKey1>" : {
"interests" : [ "x" ],
"live" : true,
"uIds" : {
"<pushKey1>" : "user1",
"<pushKey2>" : "user2"
}
},
"<pushKey2>" : {
"interests" : [ "y" ],
"live" : true,
"uIds" : {
"<pushKey1>" : "user2"
}
}
}
To get the pushKey, you can use push() method (the same as what you have done to each Request item)
Then the code will be like this if you just want to add a new uid to a request.
String requestKey = "request001";
mDatabase.child("requests").child(requestKey).child("uIds").push().setValue("user2");
Comment here if you have questions, hope this helps :)
Adding 2 cents to Frank van Puffelen answer, you can use the key from push operation as a unique identifier of your request. Plus if you use hash map to update child then your DB will not be overridden
// Create a node on Server and get key
String requestID = AdminController.getInstance().referenceFromUrl
.child(END_POINT_REQUESTS)
.push().getKey();
//use key as ID for your Object which you want to push as unique identifier of your model
requestToPush.setRequestId(requestID );
//Add updated Model Object in a Map to update DB instead of over writing
requestsMap.put(requestID , requestToPush);
//Update newly created DB nodes with data
referenceFromUrl
.child(END_POINT_REQUESTS)
.updateChildren(productsMap,
new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError != null) {
Log.e(TAG, "Error: Data could not be saved "
+ databaseError.getMessage());
} else {
Log.e(TAG, "Success : Data saved successfully.");
}
}
});
Result
This is how my firebase real-time database looks like.
{
"users" : {
"PDFZ0QOoTxYbCyruOrbiA2y1n5O2" : {
"email" : "mark#outlook.com",
"name" : "Mark Evans",
"paymentMethods" : {
"-ML5oMsCnXgfBRCh7DdO" : {
"paymentProvider" : "Google Pay",
"phone" : "+915555555555",
"userName" : "Mark Evans"
},
"-ML5oRHklHHIK33NQowD" : {
"paymentProvider" : "BHIM",
"phone" : "+911111111111",
"userName" : "Chris Evans"
}
},
"uid" : "PDFZ0QOoTxYbCyruOrbiA2y1n5O2"
},
"epyBsLU0fYOT8uc0Bo698f5SRcO2" : {
"email" : "axle#gmail.com",
"name" : "Axle Blaze",
"paymentMethods" : {
"-ML5o5Zv_3ZixCIXWBqg" : {
"paymentProvider" : "Google Pay",
"phone" : "+918888888888",
"userName" : "Axle Blaze"
},
"-ML5o9pMNucaacdU0G3P" : {
"paymentProvider" : "BHIM",
"phone" : "+911111111111",
"userName" : "Bunny Blaze"
}
},
"uid" : "epyBsLU0fYOT8uc0Bo698f5SRcO2"
}
}
}
I need to get those payment options where phone number = "+91XXXXXXXXXX" no matter from which user payment option belongs to.
For example: If i need all payment options where phone == +911111111111
Result should be :
"-ML5oRHklHHIK33NQowD" : {
"paymentProvider" : "BHIM",
"phone" : "+911111111111",
"userName" : "Chris Evans"
},
"-ML5o9pMNucaacdU0G3P" : {
"paymentProvider" : "BHIM",
"phone" : "+911111111111",
"userName" : "Bunny Blaze"
}
I have to do this in Android but since querying should be similar for any platform so I need help with how the query should be structured.
Any kind of help is appreciated. Thanks in advance.
Firebase queries work on a flat list. The value you order/filter on must be in a fixed location under each direct child node of users.
In your current structure, you can:
Search across all users for direct properties of that user, such as their email or name.
Search across the payment methods of a specific users.
The query you want is not possible on your current data structure. If you want to allow a query across all payment methods of all users, you'll need to change (or augment) your data structure to (also) have a flat list of payment methods across all users.
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value
sorry for my bad English level, I'm from Argentina.
I have the following messages data structure in Firebase:
"messages"
"-KezmqXSdKCNFFA432Uc___-KfCEwklG_y3naRDIUiY"
"messageDate": "20170620"
"messageTime": "18:44"
"message": "Hi"
"-KezFDSAADFASFFS3221___-KASDF32324SDFASD1FS"
"messageDate": "20170620"
"messageTime": "22:23"
"message": "How are you?"
Where -KezmqXSdKCNFFA432Uc, -KfCEwklG_y3naRDIUiY, -KezFDSAADFASFFS3221 and -KASDF32324SDFASD1FS are users.
My problem is that I created a childEventListener in "messages" node to receive new users messages but I am receiving all the new messages of all the users (I'm logged in one user per app) because my childListener is in "messages" node.
Is it correct that if I have 1000 users when adding a message, a new message reaches the 1000 users? (Assuming that within the app, you can check to which user that message belongs).
Thanks!
If you do a structure like similar to this:
-chats
- chatUID
- members
- userUID
- lastMessageSent:messageUID
- ... more properties
-chatMessages
- chatUID
- messageUID
- sentBy: userUID
- messageDate:""
- messageTime:""
- message:""
-userChats
- userUID
- chatUID
you can attach a listener to /userChats/userUID, which will display active chats, and a listener to /chatMessages/chatUID, which will get all chat messages for a specific chat conversation.
This way is a lot easier to setup firebase security rules, and users will only receive chat messages which they are apart of.
Thanks to #Linxy for a brilliant answer
I have created a firebase database regarding #Linxy answer
Here is the complete JSON export
{
"Chats" : {
"-Lsfsd234xda" : {
"lastMessageSent" : "-LrDEBo1-Message",
"members" : [ "-LrDEBoLokW-5mhaT3ys", "-LrDEBoLokW-5mhaT3yz" ],
"more_properties" : "goes here"
}
},
"Users" : {
"-LrDEBoLokW-5mhaT3ys" : {
"id" : "-LrDEBoLokW-5mhaT3ys",
"userDisplayName" : "Qadir Hussain",
"userEmail" : "XXXXX.XXXX#gmail.com",
"userPhotoUrl" : "https://lh3.googleusercontent.com/a-/AAuE7XXXXXXXXX"
},
"-LrDEBoLokW-5mhaT3yz" : {
"id" : "-LrDEBoLokW-5mhaT3yz",
"userDisplayName" : "Ishaq Bhojani",
"userEmail" : "XXXXXXX.XXXXXX#gmail.com",
"userPhotoUrl" : "https://lh3.googleusercontent.com/a-/AAuE7mB3KTbXXXXXXXX"
}
},
"chatMessages" : {
"-Lsfsd234xda" : {
"-LrDEBo-MessageUID" : {
"message" : "Hi there!",
"messageDate" : "10/10/2019",
"messageTime" : "10:16pm",
"sentBy" : "-LrDEBoLokW-5mhaT3ys"
},
"-LrDEBo1-MessageUID" : {
"message" : "Hello",
"messageDate" : "10/10/2019",
"messageTime" : "10:17pm",
"sentBy" : "-LrDEBoLokW-5mhaT3yz"
}
}
},
"userChats" : {
"-LrDEBoLokW-5mhaT3ys" : {
"0" : "-Lsfsd234xda",
"1" : "-Lsfsd234xda1",
"chatUID" : "-Lsfsd234xda"
}
}
}
I know it's late to answer but for future readers although Linxy's answer is neater, I would like to point out a more efficient one having been tried both structures:
ChatMessages
smallerUID_biggerUID
messageUID
sentBy : userUID
messageDate : ""
message : ""
.
.
.
.
UserChats
userUID
pairUID
lastMessage : ""
.
.
.
.
In this way, instead of first finding out the chatId then finding out which user is associated with that chatId, we can directly search which users should appear in our active chat tab and get thouse users' information (username, profilePicture). The reason for that is we can always calculate the chatId if we know the user's id we would like to message with. So for the message tab, we calculate the chatId (smallerUID_biggerUID) in client side and search for the messages in referencing it.
In order to structure your database, please read this post: Structuring your Firebase Data correctly for a Complex App. You'll find here for sure the answer to your question.
As a conclusion, try to flatten(denormalize) your database as much as possible.
Hope it helps.
this structure doesn't support what you want to do, it better to change it by using something like channels, where a channel contains the messages between two persons, so when any one of them send a message the other one will be notified.
{
"users": {
"userId": {
"conversations": {
"conversationId": {
"unseenCount": 0
},
"conversationId2": {
"unseenCount": 3
}
}
},
"conversations": {
"conversationId": {
"displayedMessage": "Message",
"members": {
"userId1": true,
"userId2": true
},
"messages": {
"messageId": {
"type": "text",
"text": "Hello",
"createdAt": "",
"senderId": "userId",
"status": "sent",
"payload": ""
}
},
"lastMessage": "my last message"
}
}
}
I think this will be the best structure for it:
{
messages: {
A8Fcn28ak9ask46: {
chat_id: "combination of sender and receivers number",
sender_id: "person sending the message",
receiver_id: "person send it to",
text: "message that the user types",
timestamp: "123981849404"
},
...
}
}
then when u get the results, you can filter through the chat_id's in forward and in reverse, which will get the conversation between two people.
Hope it helps.
I am very new to NoSQL and denormalization. However, I wish to allow the actions at SignUp within my app defined as:
If a username is already taken, then a user is not allowed to use it
If a phone number is already taken, then a user is not allowed to use it
Allow a new user to "sync" their phone number contacts with the server to determine who are presently users, and retrieve their respective uid's
I have the schema outlined as below given the quick need to check if a username/phone number is already present at user sign up, as well as the needed search and compare given if the new users contacts phone numbers are link to users already present within the app:
{
"presentUsersByPhoneNumber" : {
"1614#######" : {
"uid" : "fdb17f3a-7b7d-4aa5-9a0b-b9fb33c349de"
},
"1614#######" : {
"uid" : "99e4989b-a046-4c5f-9478-5ebd8bdc3ded"
},
"1614#######" : {
"uid" : "1783917f-00e4-47a0-b2cd-987bdf185129"
},
"1614#######" : {
"uid" : "a96da7b1-7c4e-44bc-b82e-fc75bed52bcd"
}
},
"presentUsersByUsername" : {
"ak" : {
"uid" : "a96da7b1-7c4e-44bc-b82e-fc75bed52bcd"
},
"ak2" : {
"uid" : "99e4989b-a046-4c5f-9478-5ebd8bdc3ded"
},
"ak3" : {
"uid" : "1783917f-00e4-47a0-b2cd-987bdf185129"
},
"kja" : {
"uid" : "fdb17f3a-7b7d-4aa5-9a0b-b9fb33c349de"
}
},
"users" : {
"1783917f-00e4-47a0-b2cd-987bdf185129" : {
"phoneNumber" : "614#######",
"username" : "ak3"
},
"99e4989b-a046-4c5f-9478-5ebd8bdc3ded" : {
"phoneNumber" : "1614#######",
"username" : "ak2"
},
"a96da7b1-7c4e-44bc-b82e-fc75bed52bcd" : {
"phoneNumber" : "1614#######",
"username" : "ak1"
},
"fdb17f3a-7b7d-4aa5-9a0b-b9fb33c349de" : {
"phoneNumber" : "1614#######",
"username" : "kja"
}
}
}
Is this approach going too fair in the act of denormalizaiton?
In NoSQL you should model the data for how your application needs to access it. Read this article on NoSQL data modeling for more information.
So if you need an efficient way to check whether a phone number or username is already taken, it makes sense to store mappings for those. The only thing I would probably do different there is to store them as simple types:
"phoneNumberToUid" : {
"1614#######" : "fdb17f3a-7b7d-4aa5-9a0b-b9fb33c349de"
"1614#######" : "99e4989b-a046-4c5f-9478-5ebd8bdc3ded"
},
"usernameToUid" : {
"ak" : "a96da7b1-7c4e-44bc-b82e-fc75bed52bcd"
"ak2" : "99e4989b-a046-4c5f-9478-5ebd8bdc3ded"
}
One thing I noticed in your sample data is that you have a key ak in presentUsersByUsername, but there is no corresponding child in users with that name. This typically happens because your code either aborts half-way through or because you made a mistake at some point during development.
You can prevent many of these problems by:
using multi-location updates, so that all writes are sent to Firebase as a single command
ref.update({
'/users/a96da7b1-7c4e-44bc-b82e-fc75bed52bcd/username': 'ak1',
'/usernameToUid/ak': null,
'/usernameToUid/ak1': 'a96da7b1-7c4e-44bc-b82e-fc75bed52bcd'
});
This update is safest way to change the name from the user from ak to ak1, wiping the old mapping and adding a new one.
using validation rules ensure that a user for each name exists
"usernameToUid": {
"$username": {
".validate": "newData.parent().parent().child(newData.va()).child('username').val() == $username"
}
}