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
Related
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).
When the user clicks a button it's going to check if the Document in Firestore is exists or not, if it is, then just update (increase) some value, and if it isn't create a new document with the new value.
I could just do the normal documentRef.get(), and documentRef.set(), but when I tested it with 2 user press the button at the same time. Its just put the new value of one of them. and so I used transaction.
In this transaction, first I get the documentsnapshot, and I get the value.
and then I put if statement to check if the document is exist, the first statement which is document is exist is working fine, but when I deleted the document, the else statement didn't do anything.
Is it even possible to use if statement inside Firebase Firestore transaction?
Map<String, Object> newDocumentSet = new HashMap<>();
newDocumentSet.put("sold", 1);
newDocumentSet.put("price", myProductPrice);
mDb.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot myDocumentSnapshot = transaction.get(myDocumentRef);
long newAddSold = myDocumentSnapshot.getLong("sold") + 1;
long newAddPrice = myDocumentSnapshot.getLong("price") + myProductPrice;
if(myDocumentSnapshot.exists()){
transaction.update(myDocumentRef, "sold", newAddSold);
transaction.update(myDocumentRef, "price", newAddPrice);
}else {
transaction.set(myDocumentRef, newDocumentSet);
}
return null;
}
});
I don't know what's happening, it didn't show any error, please tell me if I made some mistake or there is another way of doing this.
or there is another way of doing this..
Yes there is, even a simpler one which in fact is more efficient than a transaction because it does not require a round trip between the client and Firebase servers. Assuming that the mDb points to the correct document, to increment the sold property by 1, please use the following line of code:
mDb.update("sold", FieldValue.increment(1));
All you need is to return a Map<String, dynamic> value from the runTransaction function and inside the run transaction make sure you return a map value like
if (a) {
return "value":true
} else {
return "value":false
}
Then in the return statement of your function assign mapDynamic["value"];. Then you will now which part of your code was executed. mapDynamic is just a variable name.
This question already has answers here:
Setting variable inside onDataChange in Firebase (singleValue Listener)
(3 answers)
Closed 5 years ago.
I'm using firebase in my project and i'm trying to figure out how to fix the next issue for few hours already without success,
I have the next code:
// gameIDperm is set as 0 as default
public void GenerateID(){
ref2.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if(snapshot.hasChild(gameIDperm))
{
gameIDperm = Integer.toString(Integer.parseInt(gameIDperm)+1);
Log.d("ID XXXX" , gameIDperm); // Here it says the right number
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Log.d("ID YYYYYYYY" , gameIDperm); // Here it says gameIDperm is 0 again
}
How can i make the variable being saved in the current function and not being reset again to default?
Hope you can help!
The code inside of the firebase listener is a callback, it will be called at some point in the future but it is not specified when that will happen.
So what happens is:
You call ref2.addListenerForSingleValueEvent(..)
You execute Log.d("ID YYYYYYYY" , gameIDperm);, at this point the variable has not been changed yet.
Firebase invokes onDataChange(..) at a later date, changing the variable.
You execute Log.d("ID XXXX" , gameIDperm);
In such an asynchronous execution you cannot rely on the order of the code to tell you when things get executed.
In addition you are using the variable gameIDperm to look up a child of the firebase reference, then rewriting that variable with the contents. This is probably not correct.
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);
I have a firebase repository, and the data is structured like the following:
I am writing java code and i just want to get the value of "listname". HEre is what i do:
Firebase f = new Firebase("https://marketlist.firebaseio.com/sharedlists/list1/listname");
and then, when i look at member functions of my firebase object f, isee that there is a getName() function that returns listname, which is "list1", but there is no getValue() function here. Also, i tried
Query q = f.startAt("list1").endAt("list1");
but again, i cannot get the value. Can anybody help me with this?
Thanks
Firebase's logic is that data may change over time. So instead of exposing a value on the Firebase reference, you need to listen for changes like this:
f.on('value', function(snapshot) {
var val = snapshot.val();
});
Inside this callback function, val will now have list1 as its value.
If/when the value of listname changes, the callback will be called again and val will have the new value.
Update
I had missed that you use Java, so the above example is for JavaScript.
In Java the mechanism is very similar, but syntactically different:
f.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
String val = (String) snapshot.getValue());
}
#Override public void onCancelled() { }
});
So you attach a listener through addValueEventListener. Then the onDataChange method is invoked immediately for the initial value and whenever the referenced value changes.