Understanding Firebase ValueEventListeners onDataChange() - android

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.

Related

Firebase Realtime Database can you remove Listener when Listener is called

So I am using Firebase Realtime Database and I want to remove a listener as soon as a certain criteria is met. Here is my code:
final DatabaseReference forRequests = FirebaseDatabase.getInstance().getReference(Common.requests + "/" + FirebaseAuth.getInstance().getUid());
listenForRequests = forRequests.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
pickuprequest.riderUID = (String) dataSnapshot.child(Common.riderUID).getValue();
if (pickuprequest.riderUID != null) {
forRequests.removeEventListener(listenForRequests);
showRequestOnMap(forRequests);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
}
});
}
listenForRequests is a global variable. Just wondering if this code will work, or if there are any better solutions to this as I feel like it is very hacky.
I can't really tell what exactly your condition is really trying to express, but if you want just a single value a single time from the database (without listening to its changes over time), you can simply use addListenerForSingleValueEvent() to get a single snapshot of a node in the database.
If you're waiting for a value to appear that wasn't previously there, and you want to stop listening at the time it appears, what you're doing is fine. But you might want to listen more closely to the child of interest instead of its parent.
Your code looks pretty idiomatic to me when you want to wait for a specific value.
In fact, the code in my gist on waiting for an initial value is pretty similar:
mListener = ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
System.out.println("The value is now "+snapshot.getValue());
ref.removeEventListener(mListener);
}
}
...

How to wait until firebase save all data?

I need some help with async tasks and Firebase. I need to save n itens on Firebase Realtime database, but I don't know how to deal with the callback.
Here's my code:
TagRepository tagRepository = new TagRepository();
for (Tag tag : databaseTags){
if (user.getTags().containsKey(tag.getId())){
Completable completable = tagRepository.saveUserOnTag(String.valueOf(tag.getId()), user.getUid());
}else{
tagRepository.removeUserOnTag(String.valueOf(tag.getId()), user.getUid());
}
}
public Completable saveUserOnTag(String idTag, String userUid) {
return io.reactivex.Completable.create(emitter->{
reference.child(idTag).child("users/").child(userUid).setValue(true).addOnCompleteListener(task -> emitter.onComplete());
});
}
If I use an callback on this method, the callback will be called n times, so I don't have any idea how to know when all of them are already saved so I can proceed.
I was trying something with Completable as you can see on the method, but I really don't know how to deal with it. There is any easy way to save all data at same time or to control all data that are being saved??
According to the official documentation, you can use simultaneous updates.
Using those paths, you can perform simultaneous updates to multiple locations in the JSON tree with a single call to updateChildren(). Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.
You could try updating firebase data synchronously using Tasks.await
Change the saveUserOnTag method as follows and try
public Completable saveUserOnTag (String idTag, String userUid){
return io.reactivex.Completable.create(emitter -> {
Tasks.await(reference.child(idTag).child("users/").child(userUid).setValue(true));
emitter.onComplete();
});
Sorry I don't know Rx, but here is a way to insert all the data in one API call. The onComplete() method is the update complete callback.
Map<String, Object> values = new HashMap<>();
for (Tag tag : databaseTags){
if (user.getTags().containsKey(tag.getId())){
values.put(idTag + "/users/" + userUid, true);
}else{
tagRepository.removeUserOnTag(String.valueOf(tag.getId()), user.getUid());
}
}
reference.updateChildren(values, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
// all data saved, do next action
}
});
Hope this helps :)

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.

Firebase database OnDataChanged is not called in Android app

I realize similar questions have been asked here, but previous solutions do not seem to apply. I'm developing an android app using Firebase auth, and I'm trying to keep track of the user handles that are registered by means of a Firebase database. This is very similar to Tanishq Sharma's answer to firebase duplicate username and custom sign-in.
My problem is that the onDataChange method of the ValueEventListener is never called. As suggested by Frank van Puffelen in Firebase with java (non-android) retrive information, I tried to add some wait time, but this did not solve the problem. After the wait time has passed, the onCancelled method is always called.
The code below shows the function that should return true if a user handle is listed in the Firebase database at node "takenHandles". I know that the connection to the database is working, because I'm able to add a new handle to the database by another part of my code without any issues. Any suggestions on what could be causing this problem?
EDIT It seems I don't have permission to comment on my own question. Thanks Frank, for your quick reply! That indeed explains why writing to the database works, because I do that just after a new user has been authenticated. I should probably consider changing the database rules.
public boolean handleTaken(final String mHandle) {
final boolean[] isTaken = new boolean[]{true};
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference takenHandles = database.getReference("takenHandles");
takenHandles.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "ValueEventListener: onDataChange");
isTaken[0] = dataSnapshot.hasChild(mHandle);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "ValueEventListener: onCancelled");
Toast.makeText(MyAccountActivity.this, "Connection error, please try again", Toast.LENGTH_SHORT).show();
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return isTaken[0];
}

Android Firebase value listener no longer works after DB change

Was creating an application on one Firebase project, and now that it's complete I've changed it to another Firebase project which has the actual database (same structure, the first project was for testing, the second is the actual live database we're using). Changed it by creating the Android version in the Firebase console, and then putting in the new google-services.json file. Now, a certain bit of code that was working perfectly now refuses to do anything whatsoever, and I don't understand why. All I have done to change the code is change the Firebase Storage references, which do not affect this particular bit of code. The database structures are identical and both databases have data at that particular path. Here is the code that is affected. This code is perfectly functional on the old database, but on the new database the onDataChange method is not called whatsoever, nor is the onCancelled. Is there anything stupid I am missing that would cause this not to work? I have been able to access functions on the new database and have successfully registered an account on the new database from the application, so am puzzled as to why this doesn't work.
public void checkForOtherAgent() {
agentcode = branchNo + postcode;
count = 0;
codeCheckListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
Log.d(TAG, "onDataChange called, count = " + count);
count++;
agentcode = branchNo + postcode + count;
mDatabase.child("agent_codes").child(agentcode).addListenerForSingleValueEvent(codeCheckListener);
} else {
registerAccount();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mDatabase.child("agent_codes").child(agentcode).addListenerForSingleValueEvent(codeCheckListener);
}

Categories

Resources