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
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.
3
Answers
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.
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.