Firebase cloud function not triggering when deleting in transaction - android

in my Android app I'm deleting some data from my Firebase Database tree, in a transaction because I want to delete multiple paths from the tree at the same time. The path that is deleted in a transaction is the following:
pseudocode:
begin transaction
for all eventId's in a list delete member with memberid
/contexts/uid/contextId/events/eventId/members/memberId
end for
then delete member from
/contexts/uid/contextId/members/memberId
commit
end transaction
I use the following java code in my android app and it works very well
database.getReference(Global.DEV_PREFIX).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
for (String eventKey : eventKeySet) {
mutableData.child("/contexts/" + getCurrentUserId() + "/" + contextId + "/events/" + eventKey + "/members/" + member.getId()).setValue(null);
}
mutableData.child("/contexts/" + getCurrentUserId() + "/" + contextId + "/members/" + member.getId()).setValue(null);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
if (databaseError == null) {
callback.onSucess();
} else {
callback.onError(databaseError);
}
}
});
But now I want to have a cloud function that listens on the deletion of the member and deletes some files from the storage.
I did a lot of tests and I can not understand the behaviour. The cloud functions are triggered when deleting the member in a non transactional way. But when deleting in a transaction, the .onDelete event is never triggered in the server (nor the onWrite event). My Cloud function is defined in the following way:
exports.memberDeleteTrigerTest = functions.database.ref('/contexts/{userId}/{contextId}/members/{memberId}/')
.onDelete(event => {
console.log('Hello memberDeleteTrigerTest');
return ;
});
I tried to listen to a inner property of the member without success:
exports.memberDeleteTrigerTest = functions.database.ref('/contexts/{userId}/{contextId}/members/{memberId}/id')
.onDelete(event => {
console.log('Hello memberDeleteTrigerTest');
return ;
});
Maybe the way I delete the data in the transaction in the Android app is not the appropriate. Do you have any suggestions?
Thanks in advance

Related

Is my implementation of a Firebase Transaction correct?

I am using a transaction to update a float value in my Firebase database (see below). It works fine, except for this one time when the value didn't update. Everything else worked perfectly (you will see that there is also an object update in the onComplete). On a side note; this update to a different child in the database showed that Input was the correct value.
I have not been able to recreate this, nor can I find anything in the logs related to this particular transaction.
My question is, does this implementation of a Firebase transaction look error prone. IOW, have I implemented it incorrectly?
private void onInboundTransactionConfirm(DatabaseReference piclCount) {
piclCount.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
float P = mutableData.getValue(Float.class);
// Set value and report transaction success
mutableData.setValue(P + Input);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
String transKey = transactionRef.child(recipientId).push().getKey();
TransactionRecord inbound = new TransactionRecord("Transfer", Input, userDirectCode, UserName);
transactionRef.child(recipientId).child(transKey).setValue(inbound);
// TransactionRecord completed
Log.d(TAG, "postInboundTransaction:onComplete:" + databaseError);
}
});
}
EDIT 1:
The following warning is attached to this line: float P = mutableData.getValue(Float.class);:
Unboxing of 'mutableData.getValue(Float.class)' may produce 'java.lang.NullPointerException'
EDIT 2:
I could do this:
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
float P = mutableData.getValue(Float.class);
if (Input != 0) {
// Set value and report transaction success
mutableData.setValue(P + Input);
}
return Transaction.success(mutableData);
}
But my concern is that if the updated value does not get set (ie for some reason Input is equal to 0), would the transaction not then see the return of the non-updated value to the database as being what the user wants it to be set to, therefore not changing the value and ending successfully?

Update other nodes when the transaction is completed

I'm learning Firebase in android and I'm trying to make a room system with their respective slots. As in this case several users may want to access the same slot at the same time I decided to use Firebase transactions.
So when the user tries to log in to the slot I do:
mySlotRef1.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Slot p = mutableData.getValue(Slot.class);
if (p == null) {
return Transaction.success(mutableData);
}
if (p.getState().equals("closed")) {
return Transaction.abort();
}
// Set value and report transaction success
Slot sl1 = new Slot("slot1", idRoom, auth.getCurrentUser().getDisplayName(), auth.getCurrentUser().getUid(), "closed", Profile.getCurrentProfile().getProfilePictureUri(200, 200).toString());
mutableData.setValue(sl1);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
I think this works, i. e. when the slot is closed it only enters one user and rejects the others in the "Slot" ref. But the problem is that I also need to update two more values in the "User" directory and I can't find a way to do it "only when the user occupied the slot", that is, when the transaction was completed.
UPDATE:
This is the part where I check to see if the slot is open.
mySlotRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Slot data = dataSnapshot.getValue(Slot.class);
state = data.getState();
if (state.equals("open")) {
saveSlot(slot);
} else {
Toast.makeText(getApplicationContext(), "Slot not available", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
This not how things are working when it comes to concurent writes. None of your slots will be closed. When you are using transactions, it means that every write that will be made in the same time by different users will be made in different thread of execution. So using transactions, it doesn't matter if two or many other users will make a write operation in the same time, you'll have a correct result.
If you want to update otrher fields within another class, just put your logic inside onComplete() method. This method is triggered once the transaction is complete.

Database Error: The transaction was overridden by a subsequent set how to solve

I know this is a common error and there are many threads in SO. But nothing helped me.
Here is my problem:
My DB structure:
root
/posts/
/user-posts/userId/
Each post has few attributes including:
likeCount
likes > id : true
Whenever a post is created, it is stored in both /posts/ and /user-posts/ node. But when I likes the post, it updates in one and fails in another with the error mentioned in Question title.
Code used for inserting a post in firebase DB:
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/posts/" + key, feedValues);
childUpdates.put("/user-posts/" + userId + "/" + key, feedValues);
dbRef.updateChildren(childUpdates);
Code used for like:
public static void updateLikeInServer(DatabaseReference dbRef, final String userId) {
Log.d(TAG, "updateLikeInServer:started:");
dbRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Post post = mutableData.getValue(Post.class);
if (post == null) { // It comes as null for /user-posts/ but not for /posts/
return Transaction.success(mutableData);
}
if (post.getLikes().containsKey(userId)) {
post.setLikesCount(post.getLikesCount() - 1);
post.getLikes().remove(userId);
} else {
post.setLikesCount(post.getLikesCount() + 1);
post.getLikes().put(userId, true);
}
// Set value and report transaction success
mutableData.setValue(post);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "updateLikeInServer:onComplete: " + databaseError);
}
});
}
Since the like count need to be updated in both /user-posts/ and /posts/, I am calling the above method twice with corresponding DatabaseReference.
Arguments:
If it fails, it should fail for both the nodes /user-posts/ and /posts/. Why it's not failing for both locations?
It only fails only for 1st time like, but if you do it consecutively like > dislike > like > dislike, it succeeds for both locations from 2nd time on wards. Why so?
MOST IMPORTANT: The error comes only when I like the post created by friend. If I like my own post, no error.
Got rid of the problem with below workaround. Don't know whether this is the proper way or not, but would love to know from others if any better solution is there.
Since it only happens for 1st time transaction, I am trying to check the error and retry the same operation again which succeeds in the 2nd attempt.
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "updateLikeInServer:onComplete: " + databaseError);
if (databaseError != null && databaseError.getCode() == DatabaseError.OVERRIDDEN_BY_SET) {
// Retry the same
updateLikeInServer(dbRef, userId);
}
}
The above snippet fixed my problem. But I would love to listen best answer if possible.

Understanding Firebase ValueEventListeners onDataChange()

So I went through the documentation about how to retrieve datain Firebase: https://firebase.google.com/docs/database/android/retrieve-data
In my firebase app I am retrieving the values using the addListenerForSingleValueEvent.
Those values retrieved(here userIdKey) in the onDataChange method are used to make updates to some other places in the database.
What I have observed is that sometimes the updates are made everywhere except
at 123 (refer the code below).
DatabaseReference channelFollowersRef = mDatabase.child("followers").child(mKey);
channelFollowersRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot childSnapshot:dataSnapshot.getChildren()){
String userIdKey=childSnapshot.getKey();
/*123*/ delChannelMap.put("/user-subscriptions/" + userIdKey + "//" + mChannelKey,null);
}
delChannelMap.put("/channels/" + mChannelKey, null);
delChannelMap.put("/user-channels/" + getUid() + "/" + mChannelKey, null);
delChannelMap.put("/channels-notices/" + mChannelKey, null);
delChannelMap.put("/channels-subChannels/" + mChannelKey, null);
delChannelMap.put("/channels-subChannels-comments/" + mChannelKey, null);
delChannelMap.put("/channel-followers/" + mChannelKey, null);
mDatabase.updateChildren(delChannelMap, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
Toast.makeText(ChannelDetailActivity.this, "Channel is now deleted.", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
How to solve this issue.
Also can someone elabrate on how onDataChange works , does it get called when all the values at the node are retrieved or 'some' of the data is retrieved ?
Should one use AsyncTask in such a scenario when retrieving data.
onDataChange gives you all the underlying child nodes at a single time whenever there is a change in the child nodes.
Firebase when retrieve data it uses a different thread to perform its task so no need of using AsyncTask.
the problem in your code maybe due to this String userIdKey=childSnapshot.getKey();...where the childSnapshot.getKey(); is not firing the data u want.. You should check in LOGCAT if data is retrieved correctly.
If it is not clear yet please add Json Data of your database so that it could be easier to understand what is happening.
Your code seems to delete fanned out data from many locations.
When you perform a multi-location update() such as this, the security rules for all nodes are checked before any of the changes are made. So (unless there is a bug in the way Firebase enforces these specific security rules), the disconnect can't be caused by security rules.
If your other nodes are updated, but /user-subscriptions/" + userIdKey + "//" + mChannelKey is not deleted, then you are likely not getting any children in dataSnapshot (and thus not passing them in delChannelMap). You'll want to run through the code in a debugger to verify that.
Whenever troubleshooting issues such as this, be sure to handle onCancelled(). It is the easiest way to get an indication of why an operation failed. Although I don't think it will execute here (after all, the write operation seems to succeed), I recommend you attach a completion callback to setValue:
ref.setValue("My new value", new DatabaseReference.CompletionListener() {
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
throw databaseError.toException();
}
});
Throwing an exception like this ensures that it will be very difficult to overlook such an error next time.

How deal with Firebase offline mode and data push?

When using Firebase, it is recommanded to use the push() command to manage data list. That's really fine, it provides a unique and ordered id for data pushed on the list.
However, when Firebase goes offline (goOffline or NetworkOffline), if the app try to push a data on a list, the completion listener is not triggered, until the app goes back online : so there is no unique id until the line is on again.
1/ Is that the expected/normal behavior ?
2/ I didn"t see in the document (as far I remember), that the push command is working differently (or only in onlinemode) in offline state. Did I miss a line somewhere ?
3/ My use case deal with data that own relationship. It means I want create an object (kind of master) in a list, and then reuse this master object id (provided by the completion listener) to build the relation between the master object and all other relevants objects. How may I deal with this offline state ?
Code Example :
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stack").push().setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView3)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
Edit
Here is 4 ways to add data:
Push with Listener
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stack").push().setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView3)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
SetValue with Listener
findViewById(R.id.button4).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stackManual").child(UUID.randomUUID().toString()).setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView4)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
SetValue without Listener
findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stackManual").child(UUID.randomUUID().toString());
temp.setValue(counter++);
((TextView) findViewById(R.id.textView5)).setText(String.valueOf(counter) + " - " + temp.getKey().toString());
}
});
Push without Listener
findViewById(R.id.button6).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stack").push();
temp.setValue(counter++);
((TextView) findViewById(R.id.textView6)).setText(String.valueOf(counter) + " - " + temp.getKey().toString());
}
});
IT APPEAR THAT THE LISTENERS ARE TRIGGERED ONLY WHEN THE DATA ARE IN THE DATABASE ON THE SERVER, AND NOT ON THE LOCAL DB !
- IS THAT CORRECT ?
- IS THAT DOCUMENTED ?
Because knowing that, a straight forward asynchronous application is more difficult to build now : encapsulation of asynchronous job may not be performed if offline, may it ?
so there is no unique id until the line is on again
This last statement is not true. Firebase push ids are generated client-side and are statistically guaranteed to be unique.
You can easily check this by splitting your operation:
Firebase ref = new Firebase(getString(R.string.firebase_url));
Firebase newStackRef = ref.child("stack").push();
System.out.println("New key/push id: "+newStackRef.key());
newStackRef.setValue(counter++, new Firebase.CompletionListener() {
The logging output will show you the new push id, even when you're not connected to the network.
IT APPEAR THAT THE LISTENERS ARE TRIGGERED ONLY WHEN THE DATA ARE IN THE DATABASE ON THE SERVER, AND NOT ON THE LOCAL DB! IS THAT CORRECT ?
The completion listeners will only trigger once the data has been committed on the server.
From the guide on saving data:
If you'd like to know when your data has been committed, you can add a completion listener. Both setValue() and updateChildren() take an optional completion listener that is called when the write has been committed to the database.
However, regular event listeners (like those created with addValueEventListener()) will be triggered both for local and remote edits. These are the listeners you should use to render your UI, etc.
Following what #Andrew-lee and #Frank-fan-puffelen (thank to both agree on that please), the solution appear to be :
Create a unique id with push() command
Add a listener (Single Value) to a firebase ref of the previous created node in (1.)
Schedule your job inside the listener (here update the UI, but could be another command)
findViewById(R.id.button7).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stack").push();
temp.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
((TextView) findViewById(R.id.textView7)).setText(String.valueOf(counter) + " - " + dataSnapshot.getKey().toString());
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
temp.setValue(counter++);
}
});

Categories

Resources