Based on the official Firebase documentation (https://firebase.google.com/docs/database/android/structure-data) it's suggested to structure data in this sort of a format:
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
Here's my understanding of the callback flow (By callback I refer to onDataChange of addValueEventListener/addListenerForSingleValueEvent)
Callback for -groups-
....In a loop Callback for each of the members (to get details from -users- node)
........Another Callback if -users- has a field for which data is at a different node
And so on and so forth.
Can this sort of a structure lead to a nested callback scenario, if yes, how to avoid it ?
Related
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
I have a database with a structure, roughly like
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
But I can't figure out how to select users who are members of the same set of groups
You'd have to define an additional group_combinations or all_groups index, where you store each user under their combines the groups:
all_groups: {
"techpioneers_womentechmakers": {
alovelace: true
}
}
I'm currently developing basic social network Android app for sharing images. Already have PHP/mySQL back-end but thinking about migrating to Firebase because of some features that I like (e.g. security, fast read/write).
So, I have users, posts, followers, likes, comments (like every other social network nowadays).
Long story short, I want to know if I'm getting this right.
According to this Firebase documentation and its example I should include unique keys from some JSON trees to others like this (example from documentation):
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
Does this mean that I will have to include keys from posts, comments, followers etc. to my users tree like this:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"profileImage": "http://blabla.bla?xyzooqlL.png",
"about:" : "Exit light, enter night",
"country": "Neverland"
// Index Ada's posts in her profile
"posts": {
// the value here doesn't matter, just that the key exists
"post123": true,
"post234": true
},
// all comments this user wrote
"comments": {
"comment123": true
},
// all posts this user liked
"likes": {
"post123": true
},
"followers": {
"user123": true,
"user234": true
}
},
...
},
"posts": {
"post123": {
"image": "www.bla.bla/sadsadsa.png",
"description": "Hue hue hue",
"likes": {
"alovelace": true,
"ghopper": true,
"eclarke": true
},
"comments": {
"comment123": true,
}
},
"post234": {
"image": "www.bla.bla/233arweeq.png",
"description": "This is nice",
"likes": {
"eclarke": true
},
"comments": {
"comment234": true,
}
},
"comments": {
"comment123": {
"userId": "alovelace"
"text": "cool",
"date": "current"
}
},
...
}
}
Isn't it a little too much if I have for example 5000 followers, 2000 followings, 1500 post likes? When fetching user or any other object I will also fetch all its keys within that object. Imagine fetching 15 very active users with all this data.
If you have any suggestions how should I structure data, please let me know. Any kind of feedback will be useful.
Thanks.
In Firebase, when applying the principles of flattened data structure explained here.
If I have the structure used in their example:
// 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": { ... }
},
"users": {
"ghopper": {
"name": "user_name",
"age": 28
},
"alovelace": { ... },
"eclarke": { ... }
}
}
let's say that I want to show the list of members of channel one with all their details. That means that I have to firstly retrieve all children at the location members/one, and then query each of the users ID individually.
That means that if I want to show them in a recycler view, the retrieving of data being asynchronous, user experience may not be the best:
Which would in my case be an implementation like:
#Override
public void onBindViewHolder(final UserViewHolder viewHolder, int position) {
//getKey(position) just retrieve the key of current member in my adapter array
FirebaseDatabase.getInstance().getReference("users").child(getKey(position)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
viewHolder.setName(user.getName());
viewHolder.setPersonAge(user.getAge());
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
This is not ideal, but I cannot see another approach. Loading each member before launching the activity introduce the problem of knowing when all the users profiles have been retrieved from the database.
Any advice is welcomed.
I need to show all the posts/images from a particular facebook page that has been liked by the user. Now, the problem is I don't want to write nested requests for the graph response. This query returns the summary of the likes and it has a field that shows whether a user likes a particular object or not. Is there any way I can access this particular field?
My graph query is : {facebook-page-id}/posts?fields=full_picture,id,likes.limit(0).summary(true).
This gives data like this :
{
"data": [
{
"full_picture": "...",
"id": "462768487218398_522641711231075",
"likes": {
"data": [
],
"summary": {
"total_count": 6600,
"can_like": true,
"has_liked": false
}
}
},
{
"full_picture": "https://fbcdn-sphotos-a-...",
"id": "462768487218398_522640531231193",
"likes": {
"data": [
],
"summary": {
"total_count": 8860,
"can_like": true,
"has_liked": false
}
}
}
I need to access the "has_liked" field from the query so that when the data returns, it is already filtered according to the likes of the user.
There is currently no way to let the Graph API filter for you on the server side unfortunately. You'll have to do this on your client.