I'm working with Firebase and have seen somewhere you can work the data so that they are in groups.
In my app I'm going to have genres and sub genres of documentaries ie:
(Genre)
History {
(sub genre)
world history {
(items)
pyramids
}
hidden history {
(items)
pyrmids
}
}
As you can see some of the items will be the same. Instead of rewriting the same item over and over again, is there a way to add them to a group so both could access the same item?
There is no problem with duplicating data, when it comes to Firebase. This is a quite common practice, which is named denormalization and for that, I recommend you see this video, Denormalization is normal with the Firebase Database.
When you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
In your case, you should consider augmenting your data structure to allow a reverse lookup like this:
pyrmids
|
--- "worldHistory": true
|
--- "hiddenHistory": true
Related
I am performing add to cart feature on Android and my idea to do that is if nodes are given like this:
Nodes:
Items:
|
|--item1Key--
|
|--price:10
|
|--itemkey2--
|
|--price:20
User:
|
|--inCartItems:
|
|--itemKey1
|
|--itemKey2
I am doing this by storing only the keys of items added in the cart to inCartItems, not the price because if somehow I will admin will change the price from Items node then it will not reflect inCartItmes node when I will store the price too in inCartItems node.
Can anyone suggest me better way to do that? All answers will be
appreciated.
Now in this case when we store only the keys of items then there generates the issue given in my previous problem
Previous problem
Edit --
// Here I successfully got all the keys in lyKey
for(ItemsExploreModel favkeys : ltKey)
{
// Toast.makeText(getContext(), favkeys.getKey(), Toast.LENGTH_SHORT).show();
mDatabase.getReference().child(FirebaseVar.ALLITEMS).child(favkeys.getKey()).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot2) {
if (snapshot2.exists())
{
ItemsExploreModel adp = snapshot2.getValue(ItemsExploreModel.class);
ItemsExploreModel adp2 = new ItemsExploreModel(snapshot2.getKey());
list.add(adp);
listKey.add(adp2);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
Here I have done the same thing with Adding into Favorite.
As per to my experience working with Firebase database, it is completely fine to make nested queries.
However Dharmaraj's answer is also good (to use cloud function to update data at multiple places) but it make duplicate copies of same data and in some case cloud function failed to update the data, that time it'll return wrong data.
The structure you have prepared is good. No need to duplicate the data (Single Source of Data). So on change in data, you don't need to worry about the other places where those changes has to be performed.
However, you just need to make nested query on app side.
The nested query you are performing is wrong. You are adding ValueEventListener.
According to this doc, ValueEventListener adds continuous listener on that node of child. And as per your needs, you don't need to continuously listen to the data changes while listing card items. So I would suggest you to use `OnCompleteListener (documentation) to read item data once only that will improve your app performance as well.
I'm some kind of late to the party, but I would like to point out a few things.
If you duplicate the entire object (including the price), then it's true, if the admin/system changes a price, it won't be reflected in the cart, since there is nothing built-in that propagates that. This practice of duplicating data is called denormalization, and it's a quite common practice when it comes to NoSQL databases. The linked answer is tagged with google-cloud-firestore, but the same rules apply also in the case of the firebase-realtime-database.
As also #Dharmaraj mentioned in his answer, you can create a synchronization mechanism between the actual item and the item in the cart, by creating a function that should immediately fire, when a price is changed. But this also means that the price should be updated in all carts in which the item exists.
Is it still worth performing this operation? The answer is no, it doesn't make any sense to update such an amount of data. Why? Because there might be some users that can leave the cart as it is, without finishing the purchase. So you'll end up updating some records that don't require that. So in my opinion, the best option that you have is to check if the price of the items has changed, when the user finishes the purchase. In that way, you'll only update the price for those users who are really willing to finish the purchase.
And going forward, that's not always the best option to go ahead with. Imagine you're inside an online shop, and you find an offer with 30% off and you add that item to the cart. One minute later you want to finish the purchase but the price has changed. That's kind of weird and can be considered a really bad user experience because anyone would like to buy the item at the price it was displayed when it was added to the cart and not at some other (bigger) price. So take this situation also into consideration.
Can this denormalization be avoided?
Yes, it can. You can always read the items in the cart and keep them in sync with the actual items. When a price is changed, you can notify the user in real-time, so it can take some actions according to it. But that will only work if you'll use a persistent listener. This means that you have to use a Query#addValueEventListener() and not a Query#addListenerForSingleValueEvent(ValueEventListener listener) or a Query#get().
And the last thing, when it comes to Android, when using such code on the client, please note that there is nothing wrong with nested listeners as long as you remove them according to the lifecycle of your activity.
After fetching the inCartItems, you can then query each item's price with another query. That'll ensure you are fetching the latest prices from the seller/admin.
If you copy the prices to each inCartItem, you can still update all prices once admin changes the price RTDB triggered Cloud Function, but you can update a maximum of 16 MB of data when using Firbase SDKs at once (256 MB in case of REST API). Once you get plenty of data, you'll have to throttle the updates in a way. Firebase also offers a REST API, so users don't even have to open your app once they figure out you are updating the prices in cart only when they open the app and any client side listener triggers. Hence, I would prefer a Cloud Function in case you are duplicating the data.
Also, if you've written the code correctly, I doubt if any of the Cloud Functions will fail unless there's any edge case.
There may be some race conditions that users sees Price 1 but while they checkout, it's Price 2 already.
The first method seems better as users will always see latest price.
I have search a lot on stackoverflow and read many questions
I was having 3 indexOn problem 2 of them are solved and 1 remains
I am sorting database and have indexOn on "favorite" and "poet" which runs successfully but I need one more indexOn for numbers inside heart node.
query is running successfully but I am getting indexOn warning in android studio
I have tried using variables in place of numbers in database rule but still getting warning
Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding '".indexOn": "heart/+91916*******"' at gazal to your security and Firebase Database rules for better performance
queryFav = FirebaseDatabase.getInstance()
.getReference(reference).orderByChild(child).equalTo("heart");
above query run successfully but what should be indexOn rule
The message you get means you are running a query that has orderBy("heart/+91916*******") on a node named gazal. To efficiently run that query, you need to have an index on heart/+91916******* to that node in your security rules. But since the +91916******* part of that index probably is dynamic (i.e. you'll have a different value of +91916******* for every user of the app), you'll have to add an index for each user. That is not feasible.
In other words: you current data structure makes it easy to read the users who have hearted a specific poem. It does however not make it easy to determine the poems that a specific user has hearted. To make that equally easy, you'll want to add an additional data structure"
"user_hearts": {
"+91916*******": {
"-KjYiXzl1ancR8Pi3MfQ": true
}
}
With the above structure you can easily read the user_hearts node for the user you're interested in, and then read the poems one by one if needed.
Also see:
Firebase query if child of child contains a value
Firebase Realtime Database - index on location containing uid
As for similar questions on this topic and on ChildEventListener, there is no relevant answer, so heres mine.
I have a local SQLite DB which holds all the data, I also have Firebase realtime database which I'm updating with new entries or real time changes across all users. I'm currently doing it with the use of ChildEventListener as follows:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getDatabase().getReference();
DatabaseReference childRef = rootRef.child("my_root");
ChildEventListener eventListener = new ChildEventListener()
{
....
};
childRef.addChildEventListener(eventListener);
As for functionality, With this code I can get realtime changes on childs, get new entries, deleted childs and everything I need but there is one problem. When this specific activity with the listener loads up, the onChildAdded listener gets called enormous amounts of times for every child on this root, as stated on the documentation:
child_added is triggered once for each existing child and then again every time a new child is added to the specified path
So I though to gain focus on the items that I really need and I have done it with:
rootRef.orderByKey().startAt("-WhatTF123456789")...
But then I have lost my CRUD capabilities because it's listening to the new entries and not all of them.
So I came up with a solution. Keep node with all the changes that has been made to the FireBase database and a node with all the users that have read and made the changes to the local DB to know who needs an update, Then use addChildEventListener to this specific node. But that seems redundant.
What is my options to handle this kind of situation?
The onChildAdded listener gets called enormous amounts of times for every child on this root.
As you already mentioned and as the docs states, this is the expected behaviour. Usually, is not recommended to attach a ChildEventListener on a node (root node) that contains huge amount of data. Please be careful about this practice because when downloading large amount of data, you can get erros like: OutOfMemoryError. This is happening because you implicitly download the entire node that you are listening to, along with all the data beneath it. That data might be present as simple properties or, as complex objects. So it can be considered a waste of resource and bandwidth. In this case, the best approach is to flatten the database as much as possible. If you are new to NoSQL databases, this practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you take a look at:
This video, Denormalization is normal with the Firebase Database.
Official docs regarding Best practices for data structure in Firebase realtime database.
My answer from this post: What is denormalization in Firebase Cloud Firestore?
This article, Structuring your Firebase Data correctly for a Complex App.
This article, NoSQL data modeling techniques.
Please also note that when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
I also recommend you to see the last part of my answer from the following post:
What is the correct way to structure this kind of data in firestore?
It is for Cloud Firestore but same rules apply to Firebase realtime database.
But then I have lost my CRUD capabilities because it's listening to the new entries and not all of them.
Everything in Firebase is about listeners. You cannot get realtime updates for objects within a node, unless you are listening to them. So you cannot limit the results and expect to get updates from objects that you are not listening to. If you need to get updates for all objects within a node, you need to listen to all of them. Because this approach isn't practical at all, you can either use denormalization as explained above or to restrict the results by using queries that can help you limit the amount of data that you get from the database. Regarding your solutions, the second one is much preferred but you can also consider another approach which would be to load data in smaller chunks according to a timestamp property, or according to any other property that you need.
Edit: According to your comment:
Can you please provide tests for each solution (1.denormalization, 2.my solution) examine use of bandwidth and resources and which one is really preferred?
All data is modeled to allow the use-cases that an app requires. Unfortunately, I cannot do tests because it really depends on the use-case of the app and the amount of data that it contains. This means that what works for one app, may be insufficient for another app. So the tests might not be correct for everyone. The denormalization process or your solution is entirely dependent on how you intend to query the database. In the list above, I have added a new resource which is an answer of mine regarding the denormalization tehnique in NoSQL databases. Hope it will also help feature visitors.
I would make a root node with the name, for example, MaintenanceUpdate.
All clients are subscribed to changes here.
As soon as MaintenanceUpdate becomes = true, all clients unsubscribe from changes to the main "database". And then (when MaintenanceUpdate = false) are re-subscribed again.
At this time you are updating the database.
I have similar requirements, with Firebase and Room, while I've solved it alike this:
public class BaseModel extends BaseObservable implements IDataModel {
/** Sqlite default PK */
private int itemId = 0;
/** Firebase uniqueId */
#ColumnInfo(name = SqliteBaseHelper.KEY_FIREBASE_UNIQUE_ID)
protected String uniqueId = null;
/** Firebase lastSync */
#ColumnInfo(name = SqliteBaseHelper.KEY_FIREBASE_LAST_SYNC)
protected long lastSync = 0;
...
}
this means, when a local record has a KEY_FIREBASE_UNIQUE_ID which is null and the KEY_FIREBASE_LAST_SYNC is 0, it has to be inserted into Firebase - else it would check, when running a synchronization AsyncTask, if the local or remote record needs to be updated. this is because the main issue is, that when inserting remotely, the ChildEventListener will attempt to synchronize duplicates to the same client - unless having such indicators for the synchronization status in place, locally and remotely. the local primary keys might vary across the clients (depending for how long they were offline and how many records where locally inserted during the offline state), while the synthetic KEY_FIREBASE_UNIQUE_ID is used for identifying them; it's the "key to success".
I'm working on a client's Point-of-Sales (POS) software. I was going to use django with MySQL but the client can't pay for a host, so I decided to write it in Java with Firebase. I'm having some trouble thinking in terms of Firebase coming from MySQL.
According to the Firebase documentation, to do relations like I would in SQL, it would have to look like:
inventory : {
CD001 : {
genre : {
"CLASSICAL" : TRUE
}
}
genre : {
CLASSICAL : {
Name : "CLASSICAL"
inventory : {
CD001 : TRUE
}
}
}
Whereas in SQL I would just put the genre primary key as a foreign key in inventory. Is there a better way to do this in Firebase? It seems for every product that has the genre CLASSICAL I would have to make two updateChildAsync(). Also, any changes (like removing a genre from inventory) I would also have to loop through two DatabaseReference.
If I were to use push to get the generated primary key it would be even worse because I would have to loop through each child just to get the genre name.
I know this may not be the optimal way to make a POS, but given the constraints of the project and that I like learning new stuff, I'm going to stick with it.
In NoSQL you will often have the same values in multiple places, to allow the specific use-cases of your app. This is normal, but may take some getting used to if you're coming from a background in relational databases.
To get to grips with Firebase's NoSQL model, I recommend reading NoSQL data modeling, watching Firebase for SQL developers (for Firebase Realtime Database) and Getting to know Clouf Firestore.
For a good example of how to model many-to-many relationships, see Many to Many relationship in Firebase. Your example looks more like a categorization problem, in which case I also recommend reading Firebase query if child of child contains a value.
I have a database defined as follow
I can retrieve the cafeList as follow:
ApiManager.getInstance().mainDB.child(CafeModel.DATASET_NAME)
.orderByChild("name").startAt(searchText)
.endAt(searchText + "\uf8ff")
.addValueEventListener(new ValueEventListener()
However I am not sure how can I attach the fact that particular cafe is also set as favorite by my user.
Unfortunately, there is no way to achieve this in Firebase using only a single query. So in your case, you should query your database twice, once to get the cafe list and second to check if one of those cafe objects is favorite or not. However, there is a workaround in which you can create a new section in your user object named favoriteCafe in which you can add all user favorite cafes but this implies duplicating data. This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, i recomand you see this video, Denormalization is normal with the Firebase Database. So in this case, if you want to know user favorite cafes, you can use a single query and attach a listener on this new created location.
Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.