Child Value counter upon making payment Firebase Android - java

I am attaching the diagram of my firebase database.
https://drive.google.com/open?id=11p5xmLjHC49sqzvXYo3Au5GdNnULdnom
I want to update the value of the key "eventsregistered" upon successful payment.
The sole requirement is to increase the number of registered events of the user upon successful payment. The Payment function is handled by me. The problem that i am facing is that i can change the value of the child upon payment but the counter resets to zero evry time I launch the app. Please provide me a solution with the proper code. I have tried using .setValue method but it didn't work out.
I have tried with the following code but it didn't work out.
The following function is executed when the payment is done. The logic seems to be okay but when i reset the app the value of the key is reset to zero for no reason that could be known by me.
The code is attached below
int regEvents = 0;
String events_registered = "0";
private void btnAddRegisteredEvents() {
final String key2 = FirebaseAuth.getInstance().getCurrentUser().getUid();
final DatabaseReference databaseReferenceObj2 = FirebaseDatabase.getInstance()
.getReference().child("Users")
.child(key2).child("eventsregistered");
databaseReferenceObj2.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
events_registered = dataSnapshot.getValue(String.class);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
regEvents = Integer.parseInt(events_registered) + 1;
databaseReferenceObj2.setValue(String.valueOf(regEvents));
}
Please help me with this code and help me to update the counter of registered evets upon a successful payment. Just a reminder, the payment function is already handled by me. What is required is the code that increments the value of the key "registeredevents" after a successful payment.

Your code isn't working because of the asyncronous nature of how the data is being retrieved from Firebase. You need to make sure the read completes before you try to use its result. As it is, you start the read, then immediately try to use the result.
But worse -- you aren't protected from someone else modifying the data at the same time after the read completes. You need to do this in a transaction.
There is a good example of a counter in the documentation. But its a little more complex than you need. I think this will work for you in place of where you currently call addValueEventListener and the rest of the function:
databaseReferenceObj2.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
long newValue = 1; // If it doesn't exist, assume zero and increment to 1.
String currentValue = mutableData.getValue(String.class);
if (currentValue != null) {
newValue = Long.parseLong(currentValue) + 1;
}
// Set value and report transaction success
mutableData.setValue(String.valueOf(newValue));
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
This is written to deal with the fact that you are currently storing this number as a string, but it probably makes more sense to store it as a number (e.g. a Long).

Related

Firebase Query startAt()

I am using a query and having some trouble. I want retrieve value from 5th to 10th. I try to use startAt () the query just doesn't get anything.
public void read(){
Query query = FirebaseDatabase.getInstance().getReference("values").orderByChild("timestamp").startAt(5).endAt(10);
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for(DataSnapshot data: dataSnapshot.getChildren()){
Value upload = data.getValue(Value.class);
mUploads.add(upload);
adapter.notifyDataSetChanged();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(MainActivity.this, "Loading Quote failed", Toast.LENGTH_SHORT).show();
}
});
}
Tl;DR: Firebase database queries are cursor based, and not offset based.
When you call startAt() you have to pass in the value of the timestamp you want to start returning results at. You can't pass in offsets, as you are trying to do now.
You current code:
FirebaseDatabase.getInstance()
.getReference("values")
.orderByChild("timestamp")
.startAt(5)
.endAt(10);
Tells the database to:
Find the node called values under the root.
Order each child node by its timestamp value.
Then find a child node with value 5 and start returning results from there.
Stop returning results once it finds a child node with value 10.
As you can probably see, this won't give you any results, since none of your child nodes have a timestamp value between 5 and 10.
As said: Firebase queries are cursor based, meaning they work on knowing something about the node to start at. While many databases paginate based on offsets, Firebase doesn't and you'll need to know the timestamp value of the node to start at.
For example, if you want to start at the node you have opened in your screenshot, and then return the next 5 results, you'd do:
FirebaseDatabase.getInstance()
.getReference("values")
.orderByChild("timestamp")
.startAt(-1590413110563)
.limitToFirst(5);

Java async update from Firebase eventlistener

I have section of code that updates a variable by the return of a Firebase query. Outside of this section, I reference that variable and print it.
However, it seems to be executing as a async operation and prints out the variable before it has any values put in so I keep getting null. Is there an on complete for addListenerForSingleValueEvent?
public Map<String, String> childKV;
public Map<String, Object> allChildren;
public getChild() {
allChildren = new HashMap<>();
DatabaseReference dbRef = fb.child("inventory");
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot childSnap : snapshot.getChildren() {
childKV = new HashMap<>();
childKV.put(
childSnap.getKey(),childSnap.getValue()
);
allChildren.put(childSnap.getKey(), childKV);
}
}
#Override
...
};
dbRef.orderByKey().addListenerForSingleValueEvent(listener);
dbRef.removeEventListener(listener);
printChildren(allChildren);
}
public void printChildren(Map<String,Object>) {...}
Edit:
Like Update value of variable from Firebase query? but for Java. Does this mean I have to use reflection?
Indeed, the eventListeners have asynchronous behaviour and thus you have correctly understood your problem.
Unfortunately the solution I have is that you may have to change your approach because of this. So you should go for an event driven approach. Hence, instead of "wait for the data to come and and then do my thing", I would "when the data comes in, do my thing".
Meaning according to your problem, print the value inside the onDataChange() method of your singleValueEventListener, this would ensure that you don't get null and also you retrieve your value correctly.
Other than that, one more approach I found, when I searched though, is Task.await(). I won't be counting on it, but you may read more about that here:
StackOverflow Question
StackOverflow Answer

SearchView with Firebase RealTime Database is taking too long to bring results and it's expensive

I'm trying to get a specific user by it's mail with a searchview in my app.
I have more than 7000 users in my database and with this searchview it takes like from 5 to 10 or 15 seconds to filter and bring data
#Override
public boolean onQueryTextSubmit(String s) {
searchQuery.startAt(s).endAt(s+"\uf8ff").limitToFirst(3).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot snapshot : dataSnapshot.getChildren()){
UserPojo user = snapshot.getValue(UserPojo.class);
Log.i(TAG, "onDataChange: " + user.getEmail());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return false;
}
the problem is the time it takes to filter the data (from 5 to 15 seconds)
Is there anyway to filter faster with a better query ? or do I need to get all the data at first , store locally and then filter it there in client side ? any suggestions on how to approach this ?
Also I have noticed with the profiler that each search is taking from 4 to 8mb of network in order to get done, that's a lot of network use for just a simple text query.
See network usage here
thanks
It sounds like you may not have defined an index on the field you're searching on. If there is no index, the filtering cannot be done on the server. So in that case the server sends all data to the client, which does the filtering. This would explain why it takes longer than you expect: it has to send all 7000 user profiles to the client.
If this is indeed the cause of the problem, you can fix it by adding an index in your rules file. Say that you're ordering/filtering a list called users on a property called email, you'd do this by:
{
"rules": {
"users": {
".indexOn": "email"
}
}
}
For more on this topic see the Firebase documentation on indexing data.

Firebase Database Transaction MutableData for ArrayList [duplicate]

I am trying to use runTransaction() of Firebase database but it is not working. Here is the code I am using.
numQuestionRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
String numQuestions = (String) mutableData.getValue();
long value = Long.parseLong(numQuestions, 16);
value++;
String incHex = Long.toHexString(value);
mutableData.setValue(incHex);
return Transaction.success(mutableData);
}
#Override
public void onComplete(FirebaseError firebaseError, boolean b, DataSnapshot dataSnapshot) {
}
});
This code is activated when I press a button within my app. When I press the button for the first time since launching the app, my database does not change. But when I press the button the second time since launching the app, it updates it to the next number. I don't understand what it wrong or why it only does it on the second button press.
You'll want to follow the pattern used in the Firebase documentation for handling transactions and handle the case where there's no current value your transaction callback:
public Transaction.Result doTransaction(MutableData currentData) {
long value = 0;
if(currentData.getValue() != null) {
String numQuestions = (String) currentData.getValue();
value = Long.parseLong(numQuestions, 16);
}
value++;
String incHex = Long.toHexString(value);
currentData.setValue(incHex);
return Transaction.success(currentData);
}
The reason you need to do this is that Firebase may (and often will) execute your transaction callback multiple times and you need to cater for that.
When you first call runTransaction() the Firebase client will immediately invoke your doTransaction() callback with its current guess for the current data. Quite often this will be null.
Your code returns the next value based on the current value. In the case above, if the current value was null the new value will be 1.
The Firebase client then sends both the assumed current value and the new value to the server.
If the actual stored value is the same as the assumed current value, the Firebase server writes the new value you specified.
If the actual stored values is different from the assumed current value, the Firebase server rejects the new value and sends the actual current value to the client.
At this stage the client goes back to step 1, with the now updated assumed current value.
If this does not explain the behavior you're seeing, you might want to check what values are passed into onComplete().

Firebase onDataChanged called a lot of times (without updates) and sometimes with null values

I have the following json tree in my firebase app
--ashWoYViS3SbHtBhLpvStRleBl13
----items
-------- -KxDDW1FYMUOxea5w5ii
------------- description: "gggh"
------------- name: "gggh"
ashWoYViS3SbHtBhLpvStRleBl13 is the userId, obtaineid with FirebaseAuth.getInstance().getUser().getUid();
And -KxDDW1FYMUOxea5w5ii is the key for an item, obtained with the push function before inserting the item in the json tree.
There are more items under the items node, this is only a sample.
Then I want to read all the items to show them in a list in my app. I do the following:
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<Item> items= utils.convertChildrenToList(dataSnapshot, Item.class);
getView().setData(customContexts);
getView().showContent();
}
#Override
public void onCancelled(DatabaseError databaseError) {
getView().showError(databaseError.toException(), true);
}
};
Query query = database.getReference("/ashWoYViS3SbHtBhLpvStRleBl13/items")
.orderByKey();
query.addValueEventListener(listener);
At this moment there are 5 items in the list, and after setting the listener with addValueEventListener, I expect to receive the result in onDataChanged only once. What really happens is that I receive infinite calls to onDataChanged every few seconds. The first time the snapshot has the 5 items. The second time the snapshot is empty (null). The third time the snapshot has again the 5 items, and so on, in an infinite loop.
The database is not being updated, because of that I don't understand why I'm becoming more than one callback in onDataChanged. The data is always there, and I don't understand also why the snapshot's value is sometimes null
If I use addListenerForSingleValueEvent instead, then I receive only one result in the callback "onDataChanged". Sometimes the snapshot value is null, sometimes the snapshot has the 5 elements. In any case, it does not solve my problem.
I tried with many versions of the Firebase sdk, from v11.0.2 to firebase 11.4.2, and it happens in all the versions.
The only way to solve the problem I found, is the following.
Instead of registering the listener for the path
"/ashWoYViS3SbHtBhLpvStRleBl13/items", I register a listener for the path
"/some_prefix/ashWoYViS3SbHtBhLpvStRleBl13/items" (and obviously, I save the data using the same prefix too).
Then all works as expected, that is, I receive only on result in the callback "onDataChanged". The snapshot is not null, and I receive the next callbacks only when the data under the items node is really changed.
Or if I use addListenerForSingleValueEvent, then I receive only one callback with all the elements in the node "items".
May you say me what I'm doing wrong here? (because I don't want to use the prefix before the user id).
Thanks in advance.
Every time you want to make a query make the fetched false to make sure that onDatachange read the method but when the method inside the onDataChange want to call onDataChange with accident can't have access because your fetched is true then.
For exemple when you make insertion in onDataChange the method will call them self other time without you calling them.
Boolean fetched = false;
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (fetched == false) {
fetched = true;
List<Item> items = utils.convertChildrenToList(dataSnapshot, Item.class);
getView().setData(customContexts);
getView().showContent();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
getView().showError(databaseError.toException(), true);
}
};
Query query = database.getReference("/ashWoYViS3SbHtBhLpvStRleBl13/items")
.orderByKey();
fetched=false;
query.addValueEventListener(listener);

Categories

Resources