I am trying out firebase database and I was checking the recommended way of modeling many to many or one to many relationships.
Say I have this relationship between post and user:
user has many posts.
This is my approach for the model design:
class User{
List<String> postIds;
...
}
class Post{
String userId;
....
}
This is according to the firebase documentation here.
I like the design instead of embedding posts created by a user under user collection like the mongodb style this design is flat; so later time if we want to fetch only users on the system we dont have to pull the posts under the users as well.
But my doubt with this design is even embedding the ids within the posts could be a problem later on; imagine I have 100 users with 1000 posts each. If I want to show list of users I have to pull 100 users which means I have to pull 100,000 post ids.
Is there any lazy loading concept on firebase? ie when I fetch a user entity the postIds should not be loaded automatically; it should be loaded on demand.
There is no "lazy loading" or "load on demand" construct present in Firebase at the moment. All the data under a location is read and transferred on access. One thing you can do is separate out the User and UserInfo to two different branches so you can access users separately without pulling in the excessive data.
class User {
String id;
String name;
String gender etc.
}
class UserInfo {
List<String> postIds;
Other info maintained per user
}
In this way, you can read the users without pulling extra information.
An important rule in Firebase is to have the data as flatten as possible. You can take a look at this post, Structuring your Firebase Data correctly for a Complex App, for a better understanding.
Because there is no way in the Firebase Database to download just one property of each node, the single way to achieve this is to use a new node that hosts all those ids. So if you want an efficient way to verify the existens of an id or to count all the users, download just the list of ids. For good measure you should keep precisely that list of ids in the database.
To get all those ids, you can use this code:
DatabaseReference postRef = FirebaseDatabase.getInstance().getReference().child("Post");
postRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
String uId = ds.getKey();
Log.d("TAG", uId);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
Hope it helps.
Related
I have feed like the image in my app. I am getting list of some documents from firetsore collection and I am populating them in List and then use it in recycler view to show details of user.
When feed is clicked, I want to show ditrict3 and district4 data. Like below:
collectionRef = FirebaseFirestore.getInstance().collection("male");
collectionRef.whereIn("district", Arrays.asList("district3", "district4"))
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<FilterDetails> filterDetailsList = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
FilterDetails filterDetails = document.toObject(FilterDetails.class);
filterDetailsList.add(filterDetails);
getParentActivity().hideLoading();
}
feedItemAdapter.updateData(filterDetailsList);
}
}
});
When district1 will be clicked I will change whereEqualTo("district", "district1") and for different selection I will change.
User can change this more frequently and if I will do one time fetch then its going to cost me very much. But If I will use realtime snapshot then data will be read again and again if other users will change their info. Solutions I thought,
1) To get all the data before where clause and do district filtering on client side(app). But in this if there are 2000 users in each state. I would have to load that much data.
2) To do onetime fetch for whatever user clicks and then show them same list. I will make separate list for all fetches.
I am really confused upon this. Any insight would be helpful.
Keep in mind that it caches recently read documents locally, and won't charge you again if future queries are read form that cache. I'd recommend running some tests with addSnapshotListener and monitoring the usage that it generates. It may be a lot less tan you imagined, and at the very least you'll have more concrete data to validate/dispel your concerns.
If you're worried still about the pricing of Cloud Firestore, I recommend doing some back-of-a-napkin calculations of realistic scenarios. I highly recommend comparing Cloud Firestore with an alternative like the Realtime Database in that case. Most cloud-based databases come with a fairly unique pricing model, and only you can decide what combination of features and price work for your app.
I am trying to show to user items, he participated in (for example liked posts). Now I store in user document (in users collection) IDs of documents from another collection (posts) and when I want to show them in recycler view firstly I get IDs from user document. Then I get all posts by IDs. Is there any workaround, where I would be able to store user ID in subcollection of post document and then get query of all liked/commented/whatever posts by user? So user document will not have reference to post's IDs and in posts collection I am able to do something like:
Query ref = from db.collection("posts") get all posts where post.likedBy == user;
I do no like idea of putting all users who liked the post into post document - user downloads all ids.
posts (collection)
-postID (post document)
-authorID, ... (fields)
users (collections)
-userID (user document)
-string[] idsOfPosts (fields)
You should use Subcollections as your data model.
Documents in subcollections can contain subcollections as well,
allowing you to further nest data. You can nest data up to 100 levels
deep.
Also you can use a collection group query to retrieve documents from a collection group instead of from a single collection. The link provides you with sample code snippets in different languages.
EDIT:
Based on the use case you have provided in the comments:
I would say the way you are describing your data model to get all posts liked by a user, it would need a query inside a query. Not sure if it's even feasible or efficient.
Here is my suggestion:
Build your data model similar to the following
This way running the following query (I'm using NodeJs) would give you all posts liked by user1.
let postsRef = db.collection('posts');
const user1 = postsRef.where('Liked', 'array-contains',
'user1');
let query1 = user1.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
Output:
EDIT: (11/12/2019)
Based on what you have described in the comments, here is an idea that might solve your issue:
Instead of having a list of the Users who liked the post, you can have a reference to a Document that contains the list of users. You can reference to as many Documents as you wish.
Example:
The Documents can be even in a different Collection.
Let's say my current structure firebase realtime database:
{
chatRooms:{
chat1:{
participants:{
user1-id:true,
user2-id:true
}
participantsCount:2,
lastTimeUpdated: {TIMESTAMP},
chatRoomName:"A chatroom",
maxParticipantsCount:10
}
}
users:{
user1-id:{
name:user1-name,
email:user1-email
}
user2-id:{
name:user2-name,
email:user2-email
}
}
}
I need to display all chatrooms that contains a specific user along with all its information, and my solution now is:
#userId is currently authenticated, firebase user.
ref.child("chats").orderByChild("/participants/"+userId).equalTo(true)
which returns the list of chats containing $userId which equals to true, along with additional information to display to the user. Is there any possible way to change this query AND/OR database structure to support optimal indexing? If I wanted to add indices, it would be something like:
chatrooms:{
participants:{
indexOn:"user1-id","user2-id"
# number of ids grows as participants increase
# Not only that, I will have to add one by one manually.
}
}
How can I add indices and also at the same time do a query in Android that will display all chatrooms that a user belongs to along with their information?
AFAIK, I cannot "loop" over the keys of the child in Android
To loop over the children of a snapshot in Android do:
for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
System.out.println(childSnapshot.getKey());
}
Relevant section in the docs: https://firebase.google.com/docs/database/android/lists-of-data#listen_for_value_events
If you're asking how to display the information about each chat room that a user is in, that will take a client-side join like this:
firebase.database().ref("userChatrooms").child(firebase.auth().currentUser.uid).once(function(roomsSnapshot) {
roomsSnapshot.forEach(function(roomKey) {
firebase.database().ref("chatrooms").child(roomKey.key).once(function(roomSnapshot) {
console.log(roomSnapshot.child("name").val());
})
})
})
This type of client-side join is not nearly as slow as most developers expect, since Firebase pipelines the nested requests.
Hello guys here is my firebase database:
I want to get list of all medicines with particular symptoms.
Here is my code i.e what i have done
public void initializeMedicineListener(String node,String type,String value){
mDatabase=mFirebaseInstance.getReference();
Query query = mDatabase.child("Medicine").child("symptoms").orderByChild("name").equalTo("Neck pain");
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
medicineList=new ArrayList<Medicine>();
if (dataSnapshot.exists()) {
for (DataSnapshot medicine : dataSnapshot.getChildren()) {
Medicine data = medicine.getValue(Medicine.class);
medicineList .add(data);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
But i am getting null results.
Please guide me guys.Am i doing something wrong??
When you run a query at a location, Firebase check each child node at that location for the property that you order on and the range/condition you filter on.
mDatabase.child("Medicine").child("symptoms").orderByChild("name").equalTo("Neck pain");
So this checks the children of /Medicine/symptoms for their name property and only returns them if they have a value equal to Neck pain.
There are two problems with this:
Your JSON doesn't have a /Medicine/symptoms. Instead you have a Medicine, where each child node has a symptoms node.
Even if it did, your symptoms child doesn't have a value Neck pain. Instead you have an array, where each value may be Neck pain.
The closest you can now get to the query you want is:
mDatabase.child("Medicine").orderByChild("symptoms/0/name").equalTo("Neck pain");
This query returns medicines for which the first symptom name is equal to Neck pain. Firebase cannot perform a query across all array members to see if it contains a specific value in any position.
As usual with NoSQL databases: if you can't perform the use-case you want with your current data structure, you can typically change/expand your data structure to allow the use-case. And usually this is done by very directly mapping what you want to show on your screen to the structure in the database.
Your current data structures allows you to efficiently look up the symptoms (and other data) for a given medicine. That's great. It does however not allow you to efficiently look up the medicines for a given symptom. To allow that you can for example add a structure that maps each specific symptom back to its medicines:
symptoms
"Neck pain"
-L6hb2...bRb0: true
-L6rW...Fuxkf: true
With this additional structure (known as an inverted index or reverse index) you can now look up the medicines for Neck pain by simple loading /symptoms/Neck pain.
For more on this approach to categorization, see my answer here: Firebase query if child of child contains a value
You can't work with firebase database as relational databases .. instead of what you looking for .. you can create new node like this
medicineSymp
---neckpain
-------medicine1ID
-------medicine1ID
-------medicine1ID
this way you could get all medicines much faster and easier
I am developing an android application and using Firebase to store my data. I want to query the firebase instance to check whether the user entered email address matches one of the email in Firebase. Attaching the Firebase backend data.
My requirement is, I want to loop through the "guardians", which is the direct child of the Database instance and check whether the user entered email matches any one of the email address in that child. In the attached image, if the user entered email matches either "ram#gmail.com" or the other one, I want to do something.
databaseReference = FirebaseDatabase.getInstance().getReference().child("guardians");
databaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot data: dataSnapshot.getChildren()){
if (**I am unable to figure out how to search through entire guardians child for the email**) {
} else {
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I am unable to figure out how to search through the entire guardians child to see whether the email matches the user entered email. Any help is appreciated. Thanks in advance.
If you made your current approach work (it's a matter of adding the correct if statement) you'd be downloading the entire list of users, just to check if a specific email address is in use. This is incredibly wasteful of bandwidth, especially as your user list grows.
You should instead use a Firebase Database query to only return the user with the requested email address:
databaseReference = FirebaseDatabase.getInstance().getReference().child("guardians");
Query query = databaseReference.orderByChild("guardianEmail").equalTo("guardian#example.com");
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
... the email address is already in use
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
Be sure to add an index for the guardianEmail field, as otherwise you'll still end up downloading all data for the query.
Note that this topic has been covered quite a few times before, and there are better ways to do this check. Most of these involve creating a so-called inverted index, where you use the (encoded) email address of the user as the key. With that structure you can prevent duplicates in Firebase's server-side security rules, which is even more efficient.
For more on this and other approaches, see:
Enforcing unique usernames with Firebase simplelogin
Firebase android : make username unique
How do you prevent duplicate user properties in Firebase?
Usernames with Firebase Simple Login (email/password)
What Firebase rule will prevent duplicates in a collection based on other fields?