I'm trying to set my counter in parse to not go below zero when the score is being decremented, at the moment it can go to negative numbers. How can I set the minimum limit to be zero?
This is what I've managed to do so far:
btnPointTeamD.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
object.increment("team_d_score");
object.saveInBackground();
}
});
btnMinusTeamC.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
object.increment("team_c_score", -1);
object.saveInBackground();
}
});
Cloud code has what's called beforeSave and afterSave triggers. beforeSave is what you need here.
A beforeSave trigger contains all of the new data (note: none of the old) and you can check object.dirty("key"); to see if that field has changed. You also don't have to do any checks for this specific case.
Parse.Cloud.beforeSave("ClassName", function(request, response) {
var object = request.params.object;
if( object.get("team_c_score") < 0 ) object.set("team_c_score", 0);
response.success();
});
Some notes: If you return response.error(), the save will note go through, so this is how you validate input. A field contains illegal characters, or data you didn't expect? Throw an error so it doesn't get saved.
You also shouldn't put anything in the success response. That will cause an error.
This function gets called automatically if it exists, and will always be called. You can't skip it. Even if you update data from the dashboard, this gets called. Same with afterSave triggers, although modifying an object in those will not do anything unless you save it.
This should go in your main.js, or a file required by main.js. I have a folder for each of my custom classes. Each class has a classNameController.js, which contains the beforeSave, afterSave, initializer, and any cloud code functions relating directly to that object.
Main requires each of these controllers, which opens up all of the Parse.Cloud endpoints to the server.
beforeSave and afterSave triggers on hosted Parse.com had a 3 second timeout. I am not aware if there is a timeout for parse-server. I've never tested it. But don't have more than a couple server calls to be safe.
Related
I have 2 questions related to Firebase's transaction in the real-time database. It will be easier to explain with an example. This is just an example, not my real code. So, do not worry if there are some compile errors.
Let say I have a building. In this building, there are have some data. I have an array of floors. Each floor can have a counter of how many chairs there are on this floor. A client can have a lot of floors, so I do not want to load all of them. I just load the ones I need for this client. I need to know how many chairs there are in total even if I do not load them all. The rules can look like this:
"...":
{
"building":
{
"...":
{
},
"totalNbChairs":
{
".validate": "newData.isNumber()"
},
"floors":
{
"$floorId":
{
"nbChairs":
{
".validate": "newData.isNumber()"
},
"...":
{
},
},
},
},
},
As I said, this is just an example, not my actual code. DO not worry about code issues.
My clients can connect on multiple devices, so I need to use transactions to adjust the "totalNbChairs" when a floor changes his "nbChairs". Important, I need to set the actual number of chairs on the floor, not just decrease a value. If the "nbChairs" is 10 for a floor and the client set "8" on 2 devices at the same time, I can not do "-2" on both devices at the same time.
The transaction will look like this
void SetNbChair(String floorId, long nbChairsToSet){
FirebaseDatabase.getInstance().getReference()
.child("...")
.child("building")
.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
//first I need to know how many chairs I have right now on the floor
MutableData nbChairMutableData = mutableData.child("floors").child(floorId).child("nbChairs");
Long nbChairLong = (Long)nbChairMutableData.getValue();
long nbChair = 0;
if(nbChairLong != null){
nbChair = nbChairLong;
}
long diff = nbChairsToSet - nbChair;
//now I can update the number of chair in the floor
nbChairMutableData.setValue(nbChairsToSet);
//Update the totalNbChairs
MutableData totalNbCHairsMutableData = mutableData.child("totalNbChairs");
Long previousTotalChairLong = (Long)totalNbCHairsMutableData.getValue();
long totalChair = 0;
if(previousTotalChairLong != null){
totalChair = previousTotalChairLong;
}
totalChair += diff;
//update the value
totalNbCHairsMutableData.setValue(totalChair);
}
#Override
public void onComplete(DatabaseError databaseError, boolean committed,
DataSnapshot currentData) {
...
}
});
}
My first question is: When are downloaded the data I need to get on the client side? Because for what I see, 2 things can happen.
First, when I do this
FirebaseDatabase.getInstance().getReference()
.child("...")
.child("building")
.runTransaction
It's possible Firebase downloads everything in (".../building"). If this is the case, this is pretty bad for me. I do not want to download everything. So if all the data is downloaded for this transaction, this is really bad. If this is the case, does anyone have an idea how I can do this transaction without download all the floors?
But, it's also possible Firebase downloads the data when I do this
Long nbChairLong = (Long)nbChairMutableData.getValue();
In this case, it's far better but not perfect. In this case, I need to download "nbChairs". Wait to get it. After, I need to download "totalNbChairs" and wait to get it. If firebase downloads the data when we do a getValue(). Can we batch all the getValue I need in a single call to avoid waiting to download twice?
But I may be wrong and firebase does something else. Can someone explain to me when and what firebase downloads to the client so I will not have a huge surprise?
Now the second question. I implemented the version I show. But I can not use it before I know the answer to my first question. But, I still did some tests. Pretty fast, I found out my "transaction" callback got some "null" event if there is data in the database. Ok, the documentation said it was expected behavior. Ok, no problem with that. I protected my code and I have something like this
if(myMandatoryData == null){
return Transaction.success(mutableData);
}
and, yes, the first time, my early return is called and the function is recalled. The data is valid the second time. Ok, seems fine, but... and a BIG BUT! I noticed something pretty bad. Something to mention, I have some ValueEventListener active to know when the data changed in the database, So I have some stuff like this
databaseReference.addValueEventListener( new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
...
}
#Override
public void onCancelled(DatabaseError databaseError) {
...
}
} );
After my early return, every "onDataChange" on every ValueEventListener is called with "null". So, my code in the callback handles this like if the data was deleted. So, I got an unexpected result in my Ui, it's like someone deleted all my data. When the transaction retries and has the data, the "onDataChange" is recalled with the valid data. But until it does, my UI just shows like there is nothing in the database. Am I supposed to cancel every ValueEventListener when I start a simple transaction? This seems pretty bad. I do not want to cancel them all. Also, I do not want to redownload all the data after I restart them when the transaction is done. I do not want to add a hack to ignore deleted data while a transaction is running in every ValueEventListener. I can miss if some data is really deleted from another device when the transaction is running. What Am I supposed to do at this point?
Thanks
When you execute this code:
FirebaseDatabase.getInstance().getReference()
.child("...")
.child("building")
.runTransaction
Firebase will read all data under the .../building path. For this reason it is recommended to run transactions as low in the JSON tree as possible. If this is not an option for you, consider running the transaction in something like Cloud Functions (maybe even with maxInstances set to 1) to reduce the contention on the transaction.
To you second question: this is the expected behavior. Your transaction handler is immediately called with the client's best guess to the current value of the node, which in most cases will be null. While this may never be possible in your use-case, you will still have to handle the null by returning a value.
For a longer explanation of this, see:
Firebase realtime database transaction handler gets called twice most of the time
Firebase runTransaction not working - MutableData is null
Strange behaviour of firebase transaction
I'm having an issue with 2 separate methods, essentially the same issue where the database reference is firing and retrieving all the correct paths from the relevant nodes, but skips over the first fire on onDataChange then fires as expected afterwards giving the values needed.
The general context of these 2 methods is retrieving the value at the database reference using a code/value (specified path) to get to its relevant value. This value is retrieved and used elsewhere in the program.
I've looked at many problems regarding onDataChange not firing. Solved many of those issues elsewhere in my program but somehow these 2 methods are persisting with this issue. Ive run debug multiple times and dont understand how its showing and getting the correct paths but skips the first run on onDataChange where other methods implementing the exact same principle is running perfecting.
Im only posting the first issue
in onCreate
databaseReference_AUTH_TABLE = FirebaseDatabase.getInstance().getReference(AUTH_TABLE_TAG); verified = false;
Context is im using a dialog to authenticate a code. Check if the code exists in the database. And if so have the rest of the program run what i need it to do
public void authenticateProductID(final String code){
databaseReference_AUTH_TABLE.child(code).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(dataSnapshot.exists() && !verified){//Exists and is not verified yet
PID = dataSnapshot.getValue().toString();
verified = true;
return;
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
public void showPopupProduct_btn(View view){
final Dialog dialogProductVerification = new Dialog(this);
dialogProductVerification.setContentView(R.layout.layout_popup_product);
Button authenticate = dialogProductVerification.findViewById(R.id.btnPopupProductVerification);
authenticate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
EditText verificationCode = dialogProductVerification.findViewById(R.id.editTextPopupCode);
code = verificationCode.getText().toString();
if(noDuplicateCode(code)){
authenticateProductID(code);
if(verified){
getPackage(PID, code);
txtResult.setText(code);
}
else{
Toast.makeText(POSActivity.this, "Authentication Failed", Toast.LENGTH_SHORT).show();
}
}
}
});
dialogProductVerification.show();
}
Because onDataChange isn't fired the first time, verified is false. But 2nd button click everything is perfect.
firbase node
Basically my app will be finished when this is resolved. Any help will be much appreciated. Thank you in advance
Firebase APIs are asynchronous, which means that the onDataChange() method returns immediately after it's invoked and the callback from the Task it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, the value of your verified boolean that you are trying to use, is not populated from the callback yet. So simply creating it as a global variable won't help you at all.
Basically, you're trying to use a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to move the code that queries the second node inside the first callback (inside the onDataChange() method) so-called nested queries, otherwise, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
I want to get value of child. But I have to wait what data changed. But I don't want to get value without datachange. (without listener)
I use below method :
FirebaseDatabase.child("benim-degerim").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
snapshot.getValue().toString()
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I want to snapshot.getValue() without listener. How can I do it ?
Loading data over the internet takes time. That's why it's done asynchronously, so that the user of your app can continue using the app while the data is being downloaded.
Since there is no way to make the internet instant, downloads will always be asynchronous and thus require a listener (or for other frameworks, some other form of callback).
The fastest way I've found to get used to asynchronous methods is to reframe your problem from "first get data, then do something with it" to "when we get the data, do something with it". This typically means that you move the code that does "something" into the onDataChange() method.
I am new to Firebase and need some help with a query to retrieve data from a table. I am currently able to access and retrieve the data that I need from firebase, however, the timing is the problem I am having an issue with.
From everything I've seen, the firebase database requires me to add event listeners to the Query or DatabaseReference objects. I am trying to download the contents of a node called "questions" before a method to display the question contents is called, however, I cannot control the timing of the firing of the event which downloads the data, and as a result my display method is always called before the firebase event fires.
How can I execute a query when I want, and be sure it will be completed before a certain section of my code executes? I am used to traditional RDBs where you execute a query and get its results and then move forward with your logic. The need to use an event handler with firebase is what I am having a hard time with. I have even tried moving the definition of the firebase reference object and the event handler into onCreate() and moved the code that calls my display method into onStart() without any success - same problem. The data I am trying to get does not change so I only need to download it once at the beginning to have available for the display method.
Here is an image of my "questions" node which is a child of the root.
image of the child "questions" node on my firebase DB
Here is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get Firebase DB reference
firebase = FirebaseDatabase.getInstance();
fdbRef = firebase.getReference("questions");
// [START Question_event_listener]
fdbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Questions object and use the values to update the UI
objQuestions = dataSnapshot.getValue();
Log.w("Firebase:", "In Firebase ValueEventListener");
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Questions failed, log a message
Log.w("Firebase Error:", "onCancelled:", databaseError.toException());
Toast.makeText(ReviewActivity.this, "Failed to load question!", Toast.LENGTH_SHORT).show();
}
});
//. . . remaining onCreate logic removed for simplicity
} //end of onCreate
#Override
public void onStart() {
super.onStart();
// I moved this logic from onCreate to onStart but did not help...
// Firebase retrieve must execute before I call any of these
if (list_type == MainActivity.LIST_UNREVIEWED_DOCS)
displayNewReviewForm();
else if (list_type == MainActivity.LIST_REVIEWS)
displayCompletedReview();
else // (list_type == MainActivity.LIST_DRAFTS)
displayDraftReview();
}
Other alternatives if I can't get this resolved may be to move this retrieve logic to the prior Activity in my sequence and pass the retrieved data as an extra to this activity - but that seems really silly to have to do such a thing. I would think I should be able to get data from a DB when I need it... not when it feels like giving it to me.
I appreciate any help getting me past this issue.
Your code is downloading the snapshot data containing all the data at the first go only, and with Firebase, you cannot download data timely, you can only do it through different references.
What I would suggest you to do is, to have a DatabaseReference of q01, q02 respectively and then call data as in when required.
If your Keys "q01", "q02" are static, which they are looking at the scenario. I would suggest you to have their DatabaseReferences:
question_one = firebase.getReference("q01");
question_two = firebase.getReference("q02");
question_three = firebase.getReference("q03");
//Once you have the reference, you can call their ValueListeners respectively
question_one.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Questions object and use the values to update the UI
objQuestions = dataSnapshot.getValue();
Log.w("Firebase:", "In Firebase ValueEventListener");
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Questions failed, log a message
Log.w("Firebase Error:", "onCancelled:", databaseError.toException());
Toast.makeText(ReviewActivity.this, "Failed to load question!", Toast.LENGTH_SHORT).show();
}
});
After looking at this a bit more, I came up with 2 possible solutions to the problem I had.
The first one I sort of mentioned already in my original question post, however it's not ideal in my opinion. It basically involves relocating the firebase retrieve logic to the prior Android Activity and passing the retrieved data to the Activity I need it in as an Extra. In my case the data is a HashMap so I would need to use the serialize versions of the methods to pass the serialized content to the desired Activity.
The best solution, is much simpler. I basically relocated the logic that I had in the onStart() function (which is calling my custom display methods) and moved it inside of the Firebase Event Listener's onDataChange() method, right after the call to dataSnapshot.getValue(). This ensures that I get the data before I call my display methods. This seems to be working well now.
Let me to start explain my problem. There is repository with some explanations, but there are no methods how to get collection or json file from Meteor server(only insert). Also author did not explain properly methods onDataChanged, onDataAdded etc.
public class Login extends Activity implements MeteorCallback{
public static Meteor mMeteor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mMeteor = new Meteor(this, "some_socket_it_doesn't_matter");
mMeteor.setCallback(this);
}
}
public class ListOfElements extends ListFragment implements MeteorCallback{
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String subscriptionId = Login.mMeteor.subscribe("notifications");
Log.d("Log", subscriptionId);
}
}
I didn't understand how i have to use subscription or how to get collection from server. Why there are only insert methods in github repository and no get? I really have no idea how make the code to get collection, use subscribe and so on. There are no any understandable explanations in the network. Please, can you help me with this by explaining how to realize getting, subscribing in this code.
There are two special things about Meteor: It works asynchronously and it has been designed specifically for real-time applications. Thus it has a few different concepts for retrieving data and for some other tasks.
In a synchronous application, you would just call insert(...) and immediately get the method's return value, e.g. a boolean value for success/error or a numeric value for the number of rows that have been inserted.
You would call get(...) and immediately receive a collection of rows as the method's return value.
But in Meteor, everything is asynchronous. This means that you get the results not immediately, but a few (milli)seconds later, in a callback method.
When you call insert(...), this is not so important, as you have noticed. You just call this method and often forget about the result, i.e. you don't wait and check for the result because insertions are usually successful. But this method is still asynchronous and you could (and sometimes should) listen for the result which will arrive a few (milli)seconds later, again.
When you want to call get(...), this would be possible in theory, with the important point again being that it's asynchronous. So you would say "get me all chat messages from the last 5 minutes". There would be no result or return value, as usual, but the result would arrive a short time later, asynchronously, in a callback method that you define. This is what onDataAdded(...), onDataChanged(...) and onDataRemoved(...) are for.
Now it's not clear, yet, why you can't call get(...) and wait for data to arrive in those methods.
The answer to that question is Meteor being designed for real-time applications. This is why you can't say "get me all chat messages from the last 5 minutes". Instead, you have to say "I want to subscribe to all chat messages from the last 5 minutes and always be updated about changes".
So, in Meteor, you subscribe to data sets instead of requesting them via get(...).
All in all, this means the following:
If you want to get some messages, you subscribe to your data set that holds those messages.
When the initial rows are sent (!) and whenever new rows are added to the collection, you receive those in your onDataAdded(...) callback. When rows are modified, you receive those changes in your onDataChanged(...) callback. And, finally, when rows are deleted, you are informed about those deletions in your onDataRemoved(...) callback.
When you don't want to get updates for your data set anymore, you unsubscribe from that set. This is optional.
With the Android-DDP library in your Android application, it translates to the following:
final String subscriptionId = mMeteor.subscribe("chats");
public void onDataAdded(String collection, String docID, String json) { ... }
mMeteor.unsubscribe(subscriptionId);
As you can see, what you have to learn is really Meteor and not the library Android-DDP. Meteor has some new concepts that one has to understand. But when you know how Meteor works, translating those things to Android-DDP is really simple and only a matter of looking up the method names.