In transaction I only want to write data if data not present
DocumentReference callConnectionRef1 = firestore.collection("connectedCalls").document(callChannelModel.getUid());
firestore.runTransaction(new Transaction.Function < Void > () {
#Override
public Void apply(Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction.get(callConnectionRef1);
Log.d(TAG, callChannelModel.getUid());
if (!snapshot.exists()) {
//my code
transaction.set(callConnectionRef1, model);
} else {
//do nothing
}
return null;
});
You can see in my Document reference is uid based and in my log I am printing uid
So where uid's data not exist my Log prints only once and I call transaction.set() elsewhere it keep showing Log of uid where data exists already it looks like my transaction keep running if I don't call transaction.set()
How can I stop it.
It happened to me too on Android. The transaction performs 5 attempts to apply itself, and only then the onFailure() function is called (even if you throw an exception in the apply() function).
But looks like this is the expected behavior:
See here: https://googleapis.dev/python/firestore/latest/transaction.html
Notice MAX_ATTEMPTS defaults to 5.
In this github issue https://github.com/firebase/firebase-js-sdk/issues/520 there's a request to add a "number of retries" option.
There is an example given in the documentation, just throw an exception and it will exit the transaction and stop executing.
db.runTransaction(new Transaction.Function<Double>() {
#Override
public Double apply(Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction.get(sfDocRef);
double newPopulation = snapshot.getDouble("population") + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, "population", newPopulation);
return newPopulation;
} else {
throw new FirebaseFirestoreException("Population too high",
FirebaseFirestoreException.Code.ABORTED);
}
}
}).addOnSuccessListener(new OnSuccessListener<Double>() {
#Override
public void onSuccess(Double result) {
Log.d(TAG, "Transaction success: " + result);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Transaction failure.", e);
}
});
DocSnippets.java
Related
I've been stuck on this for months. I have removed some minor details from the function but nothing major. I have this https cloud function that ends a session and then uses endTime and startTime to calculate bill which is then returned to the client.
startTime is retrived from the realtime firebase database (which the session starter function put there).
My code snippet:
exports.endSession = functions.https.onRequest(async (req, res) => {
console.log("endSession() called.")
if(req.method == 'GET'){
bid = req.query.bid
session_cost = req.query.sessioncost
}else{
bid = req.body.bid
session_cost = req.body.sessioncost
}
start_time_ref = admin.database().ref("/online_sessions/").child(bid).child("start_time")
start_time_snapshot = await start_time_ref.once('value')
console.log("start_time_snapshot: "+start_time_snapshot.val())
start_time_snapshot = moment(start_time_snapshot.val(), 'dddd MMMM Do YYYY HH:mm:ss Z');
endDateTime = getDateTime()
console.log("startTime: " + start_time_snapshot.toString())
console.log("endTime: " + endDateTime.toString())
hour_difference = getHourDifference(start_time_snapshot, endDateTime)
bill = ride_cost * Math.ceil(hour_difference)
console.log("bill: "+bill)
var s_phone
sSessionlinks_ref = admin.database().ref('/sSessionlinks/')
sSessionlinks_snapshot = await sSessionlinks_ref.once('value')
sSessionlinks_snapshot.forEach((sid)=>{
if(sid.val() == bid){
s_phone = sid.key
}
})
s_fcm_token_ref = admin.database().ref("/s/").child(s_phone).child("FCM")
s_fcm_token_snapshot = await s_fcm_token_ref.once('value')
try{ // telling another client that session has ended.
await admin.messaging().send({
data: {
type: "sessionCompleted",
bill: bill.toString()
},
token: s_fcm_token_snapshot.val()
})
}catch(error){
}
//deleting this session from online sessions
online_session_ref = admin.database().ref('/online_sessions/').child(bid)
await online_session_ref.remove()
//puting this session as available
available_session_ref = admin.database().ref('/available_sessions/')
json = {}
json[bid] = s_phone
await available_session_ref.update(json) // session made available
res.status(200).send(bill.toString())
// here it *sometimes* returns 304 and then restarts but since i've already removed online_session_ref I cannot get startTime again because its removed with online_sessions so it fails.
// return
})
When its first called. It does all the calculations correctly but responds with a 304. So (I think the client) resends the request and the function is called again but since session is destroyed so it cannot calculate startTime.
Why is it that when its first called, even though all the calculations happen correctly it returns a 304 and not a 200? This problem doesn't happen all the time. It usually happens when this function is called after a long time but I'm not certain with that. I don't know what causes this.
Helper functions I've used:
function getHourDifference(s, e){
return moment.duration(e.diff(s)).asHours()
}
function getDateTime(){
d = moment.utc().utcOffset('+0530')
return d
}
When function end first time the text payload is Function execution took 794 ms, finished with status code 304
When it runs the second time (where it cannot get startTime cause its been removed in first run. There shouldn't be a second run in the first place.), the payload text is Function execution took 234 ms, finished with status code 200 (its 200 but return NaN becuase it cannot do calculation without startTime.
EDIT:
As some of you asked me to tell how the the function is being called:
Its being called from and android app using Volley. The parameters are assured to not be null. The code segment to call that function is:
// Setting the button's listeners.
endSessionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.PROGRESS_TYPE);
progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
progressDialog.setTitleText("Ending session...");
AlertDialog endDialog = new AlertDialog.Builder(getContext()).create();
endDialog.setTitle("End Session?");
Log.e("sessioncost", String.valueOf(session_cost));
endDialog.setButton(Dialog.BUTTON_POSITIVE, "Yes", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.show();
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(getContext());
String url = "https://us-central1-something-something.cloudfunctions.net/endSession?bid=" + bid + "&sessioncost=" + session_cost;
Log.e("end sesion button", url);
// Request a string response from the provided URL.
StringRequest endSessionRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(final String response) {
progressDialog.dismiss();
Toast.makeText(getContext(), "Session Completed", Toast.LENGTH_LONG).show();
progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.SUCCESS_TYPE);
progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
progressDialog.setTitleText("Session Completed: Bill");
progressDialog.setContentText("Please pay ?" + response + " to s.");
progressDialog.setCancelable(false);
progressDialog.show();
changeState('1');
bill_in_paise = Float.parseFloat(response) * 100;
Log.e("bill", bill_in_paise.toString());
progressDialog.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sweetAlertDialog) {
sweetAlertDialog.dismiss();
Intent intent = new Intent(getContext(), PaymentActivity.class);
intent.putExtra("amt", bill_in_paise.toString());
startActivityForResult(intent, REQUEST_CODE);
}
});
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getContext(), error.toString(), Toast.LENGTH_LONG).show();
}// onErrorResnponse - END
});
// Add the request to the RequestQueue. Cuz volley is asyc af.
queue.add(endSessionRequest);
// VOLLEY REQUEST - END
}
});
endDialog.setButton(Dialog.BUTTON_NEGATIVE, "No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getContext(), "Session not cancelled. " + which, Toast.LENGTH_LONG).show();
}
});
endDialog.show();
}
}
}); // endSessionButton onclick - end
UPDATE: #tuledev helped fix the 304 with a work around but the problem is still here. Even when the status code is 200 the cloud function is somehow called again and I get a NaN bill. At this point I don't know what's causing this.
The 304 status comes because the response is the same as the previous. Firebase cloud responses 304 and the client will get the cached data.
For preventing 304 status, we can return the value + uid of the bill, or something make the response diff from the previous.
As OP and I discussed, the 304 is solved but the problem's still here. So I think the problem comes from the client side.
Hope somebody can HELP him.
Edit: OP here, I changed the client code to using okhttp instead of volley and after testing for 2 days everything seems fine. Tuledev's answer did fix 304 but even after 200 the problem persisted. Just use okhttp instead of volley.
i'm trying to make a simple check before saving an object on Parse.
I'm using the "beforeSave" method to check if object has necessary fields setup correctly before saving.
The problem is that when i try to get a field of my object it return always undefined even if it is setup correcly in log!
Input: {"original":null,"update":{"ACL":{"abcdefghi":{"read":true,"write":true}},"isDeleted":false,"lastEdit":0,"name":"testobject","uuid":"109d0b30-1ad5-408b-ba49-2ce024935476"}}
and cloud code:
Parse.Cloud.beforeSave("MyObject", function (request, response) {
// return always an error with uuid printed
response.error("Object uuid: " + request.object.get("uuid"));
}
This is a sample code, not my real code but as you can see the object passed to this function has "uuid" field inside but request.object.get("uuid") return always "undefined":
Console log:
Result: Object uuid: undefined
Official documentation say to access object fields in this way, so is this a Parse bug or i'm doing some mistakes?
EDIT 1:
As suggested i tryied to log the object in this way:
console.log("Object: " + JSON.stringify(request.object));
console.log("Request: " + JSON.stringify(request));
The result is:
Object: {}
Request: {"object":{}, <some other fields..>}
Any idea?
EDIT 2:
I reproduced correcly the error and it seems to be a bug with ACL;
First i created a new object extending ParseObject (i'm working on Android):
#ParseClassName("Person")
public class ParsePerson extends ParseObject {
public void setName(String name) {
put("name", name);
}
public void setAge(int age) {
put("age", age);
}
public String getName() {
return getString("name");
}
public int getAge() {
return getInt("age");
}
}
And than my background thread it will create and save a test object in this way:
ParsePerson parsePerson = new ParsePerson();
parsePerson.setName("Tom");
parsePerson.setAge(45);
try {
parsePerson.save();
} catch (ParseException e) {
Debug.e("Error code: "+ e.getCode(), e);
}
Than i uploaded this cloud code that does nothing else than log:
Parse.Cloud.beforeSave("Person", function (request, response) {
// log person object
console.log("Object: " + JSON.stringify(request.object));
// respond always success to save the object
response.success();
});
Parse.Cloud.afterSave("Person", function(request) {
// log person object again
console.log("Object: " + JSON.stringify(request.object));
// respond success
response.success();
});
After running it works and it saves correctly the object in the new table. Logs:
Input: {"original":null,"update":{"age":45,"name":"Tom"}}
Result: Update changed to {"age":45,"name":"Tom"}
Object: {"age":45,"name":"Tom"}
The last thing i tryid to do was to set ACL to this object editing android code in this way:
ParsePerson parsePerson = new ParsePerson();
parsePerson.setName("Tom");
parsePerson.setAge(45);
parsePerson.setACL(new ParseACL(ParseUser.getCurrentUser())); // new line
try {
parsePerson.save();
} catch (ParseException e) {
Debug.e("Error code: "+ e.getCode(), e);
}
And after running everything this is the log:
Input: {"original":null,"update":{"ACL":{"userAcl":{"read":true,"write":true}},"age":45,"name":"Tom"}}
Result: Update changed to {}
Object: {}
So is this enough?
EDIT 3:
The only solution i found (this is more a workaround) is to save the object without ACL from Android code and then set ACL in beforeSave on cloud code:
Parse.Cloud.beforeSave("Person", function (request, response) {
// setup ACL for this object
var user = request.user;
var newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(user.id, true);
newACL.setWriteAccess(user.id, true);
request.object.setACL(newACL);
// log person object again
console.log("Object: " + JSON.stringify(request.object));
}
I had a very similar problem. I was making changes to an existing project and when I uploaded the code I started getting error where no errors had been before.
I finally solved it by going back to jssdk version to 1.4.0 in .parse.project
Maybe it solves your problem also.
I'm trying to get response from U2F Token in GWT project using this source code:
public class Test implements EntryPoint {
#Override
public void onModuleLoad() {
Window.alert("Alert 3:"+u2FTest());
}
public static native String u2FTest()/*-{
var respond = {rep: "Clear"};
var RegistrationData = {"challenge":"dG7vN-E440ZnJaKQ7Ynq8AemLHziJfKrBpIBi5OET_0",
"appId":"https://localhost:8443",
"version":"U2F_V2"};
$wnd.u2f.register([RegistrationData], [],
function(data) {if(data.errorCode) {
alert("U2F failed with error: " + data.errorCode);
return;
}
respond.rep=JSON.stringify(data);
alert("Alert 1: "+respond.rep);
});
alert("Alert 2: "+respond.rep);
return respond.rep;
}-*/;
}
for some reasons I get The Alerts like so:
(Alert 2) first with "Clear" result
(Alert 3) with "Clear"
(Alert 1) with Token response
Normally I've to get (Alert 1) with Token response then 2,3. So how can I stop execution until I'll get the token response
Thank you,
Embrace asynchronicity!
public static native void u2FTest(com.google.gwt.core.client.Callback<String, Integer> callback) /*-{
// …
$wnd.u2f.register(regReqs, signReqs, $entry(function(response) {
if (response.errorCode) {
callback.#com.google.gwt.core.client.Callback::onFailure(*)(#java.lang.Integer::valueOf(I)(response.errorCode));
} else {
callback.#com.google.gwt.core.client.Callback::onSuccess(*)(JSON.stringify(response));
}
}));
}*-/;
(don't forget to wrap callbacks in $entry() so that exceptions are routed to the GWT.UnhandledExceptionHandler, if there's one)
I am developing cordova plugin for the first time and stuck in the following issue.
I have created a class extending CorodvaPlugin and override execute method as given . What I want is after the asynctask has completed it background task, response is returned to the JS and values are displayed on the HTML but whats happening sometimes values are displayed and sometimes not.Any help would be appreciated.
#Override
public boolean execute(String action, JSONArray args,
CallbackContext callbackContext) throws JSONException {
try {
context = this.cordova.getActivity().getApplicationContext();
this.mMyCallbackContext = callbackContext;
new WSCall().execute();
PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
pluginResult.setKeepCallback(true);
mMyCallbackContext .sendPluginResult(pluginResult);
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
and in the Async Task post execute I have done this
#Override
protected void onPostExecute(String result) {
PluginResult result_;
if(groups!=null)
result_ = new PluginResult(PluginResult.Status.OK, groups);
else if(ret_msg!=null)
result_ = new PluginResult(PluginResult.Status.OK, ret_msg);
else
result_ = new PluginResult(PluginResult.Status.OK, "");
result_.setKeepCallback(false);
mMyCallbackContext.sendPluginResult(result_);
pDialog.dismiss();
}
Use this link
and don't return true from execute method ,return Pluginresult only.
REPOSITORY METHOD
public int CalculateSoundVolume(string roomName, int currentUser)
{
{
//Business Logic
return finalApplauseVolume; //**say returning 75**
}
catch (Exception ex)
{
_logger.LogException(ex);
throw;
}
}
WEB API CONTROLLER
public IHttpActionResult CalculateSoundVolume()
{
try
{
//Some Logic
var result = _applauseRepository.CalculateSoundVolume(huddleName, currentUser);
return Ok(result); // **it returns 75 here in result**
}
catch (Exception ex)
{
_logger.LogException(ex);
throw;
}
}
CLIENT SIDE CONTROLLER (ANGULAR JS)
public calculateSoundVolume()
{
var promise = this.applauseService.calculateSoundVolume();
promise.then((res) => {
this.$log.debug("Sound Volume : ", res);
// Getting Resource [0] : '7' and [1] : '5'
});
}
SERVICE
calculateSoundVolume()
{
return this.soundVolume.get().$promise;
}
Now here the scenario is i am returning an integer value from my Repository method. (say 75). I the WEB API controller the value is recieved as 75 in result.
But the issue is in "res" in my client side controller i am recieving a Resource as [0]:'7' and [1]: '5' i.e the actual and expected value is not recieved. Please suggest any solution
This same issue was happening to me. Turns out the $promise, instead of returning the int, returns an object that breaks the digits of the integer into different indexes in an array along with some other information the promise uses. I was able to resolve the issue by wrapping the integer with a JObject and passing that from the Web API instead of the integer.
So your Web API would look like this:
public JContainer CalculateSoundVolume()
{
try
{
//Some Logic
var result = new JObject(new JProperty("soundVolume", _applauseRepository.CalculateSoundVolume(huddleName, currentUser)));
return result;
}
catch (Exception ex)
{
_logger.LogException(ex);
throw;
}
}
and your client side controller would change to this:
public calculateSoundVolume()
{
var promise = this.applauseService.calculateSoundVolume();
promise.then((res) => {
this.$log.debug("Sound Volume : ", res.soundVolume);
});
Hope that helps
-- Edit --
I should clarify that I am using the Newtonsoft library for JSON in the above code. I found another stackOverflow question which relates Here. In conclusion Chandermani is correct that you should try sending a valid JSON object back from the server instead.