Whenever I updated the value 1st time, it works fine. But whenever I updated it again within a minute. The problem started, Firebase keeps updating, and as a result the apps crash. Can anyone help?
private void UpdatedStatus(String roomkey,String rents_not){
reference= FirebaseDatabase.getInstance().getReference("Room");
Query query = reference.orderByChild("Roomkey").equalTo(roomkey);
if (reference != null) {
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot1 : snapshot.getChildren())
{
String childPath=dataSnapshot1.getKey();
referenceUpdated = FirebaseDatabase
.getInstance().getReference("Room").child(childPath);
HashMap<String, Object> map = new HashMap<>();
map.put("Status", ""+rents_not);
referenceUpdated.updateChildren(map).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Updated", Toast.LENGTH_SHORT).show();
finish();
dialog.dismiss();
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
}
In your current code, you do the following:
Set up a query for rooms with "Roomkey" == roomkey.
Attach a listener for updates on rooms that match the query
Upon getting results, set each matching room's status to rents_not
When you call UpdatedStatus("room1", "AVAILABLE"), everything works "fine" (the room "room1" is changed to AVAILABLE, then the room is changed to AVAILABLE again and finish.). Because you used addValueEventListener to listen for updates to the matching rooms, your code gets rerun when the data changes. This is why I said the status is changed twice.
Next, when you call UpdatedStatus("room1", "RENTED"), you experience the crash. After calling UpdatedStatus the second time, the status is changed to "RENTED", which fires both of the listeners from UpdatedStatus("room1", "AVAILABLE") and UpdatedStatus("room1", "RENTED"), constantly switching between "RENTED" and "AVAILABLE" forever.
You can fix this loop (and the double write) by using addSingleValueEventListener() instead. This will execute and read from the query only once.
FirebaseDatabase mDatabase = FirebaseDatabase.getInstance();
DatabaseReference mRoomsRef = mDatabase.getReference("Room");
private void UpdatedStatus(String roomkey,String rents_not){
Query roomQuery = mRoomsRef.orderByChild("Roomkey").equalTo(roomkey);
roomQuery.addSingleValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot childSnapshot: snapshot.getChildren())
{
String childPath = childSnapshot.getKey();
DatabaseReference childRef = childSnapshot.getReference();
HashMap<String, Object> map = new HashMap<>();
map.put("Status", ""+rents_not);
childRef.updateChildren(map)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Updated", Toast.LENGTH_SHORT).show();
finish();
dialog.dismiss();
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.e(TAG, "Failed to get rooms for key: " + roomkey + "; " + error.getMessage());
}
});
}
}
As auspicious99 answered, since you update the same node that your query is listening for, your own call to updateChildren ends up triggering onDataChange again. And since you then update the node again, it triggers again, and again, and again... etc.
There are a few solutions for this:
Use addListenerForSingleValueEvent, which triggers only once and stops listener after that. This is the simplest change, as you can just change
query.addValueEventListener(new ValueEventListener() {
to
query.addListenerForSingleValueEvent(new ValueEventListener() {
You can also choose to detect whether the Status field is already set to rents_not and skip the update if it is. Inside the loop in onDataChange, you can do this with:
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot houseSnapshot: snapshot.getChildren()) {
DataSnapshot statusSnapshot = houseSnapshot.child("Status");
String status = statusSnapshot.getValue(String.class);
if (!rents_not.equals(status)) {
statusSnapshot.getReference().setValue(rents_not).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Updated", Toast.LENGTH_SHORT).show();
finish();
dialog.dismiss();
}
});
}
}
You'll note that I've updated the code here a bit, since you can get the reference from the snapshot, and perform the update at the lower level.
These will both fix your current problem, so pick whichever works for the use-case. But since your new value of the Status node depends on its current value, consider using a transaction to update it, to eliminate the risk that two users will both post conflicting updates.
It looks like you may have an unintended loop, as inside your onDataChange, you are doing referenceUpdated.updateChildren(map), which changes data in your firebase database. This triggers onDataChange again, and so on.
You could break the circle by setting a Boolean/flag and checking it as needed.
Related
Following is screenshot of my Firebase realtime database. How can I notify a user whenever the item worker_id is changed?
I've tried the following code, but it notifies about each type of data change. I want notification specific to change in worker_id:
private void CheckDataChange(){
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("posts");
Query query = myRef.orderByChild("workType");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d("Firebase","onDatachange");
for(DataSnapshot ds : dataSnapshot.getChildren()) {
try {
if(!ds.child("worker_id").getValue(String.class).equals("0")) {
Toast.makeText(MainActivity.this, "Request accepted", Toast.LENGTH_SHORT).show();
}
}catch (Exception ex){
Log.e("Firebase",ex.getMessage()+ex.getCause());
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d("Firebase", databaseError.getMessage());
}
};
query.addValueEventListener(valueEventListener);
}
There is nothing built into Firebase to raise an event only when the worker_id in your current structure changes.
The most common way to implement something like that would be to create a map mapping ds.getKey() to the value of worker_id when onDataChange is first called, and then comparing the value you get against that for any updates.
I try to make a login activity using Firebase realtime database.
My database is filled as shown bellow when signing up:
Upon sign-in I want to initialize a public static User so that I can retrieve it in my main activity and update UI accordingly but the dataSnapshop of the ValueEventListener returns null.
Here is the code of my addListenerForSingleValueEvent :
currentUserReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
currentUserUsername = dataSnapshot.child("/username").getValue(String.class);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
currentUserUsername = "ERROR";
}
});
// Get every information into a User class
MainActivity.CURRENT_USER_SESSION = new User(
currentUserUsername,
user.getEmail(),
user.getUid()
);
Anyone has an idea to help me. Or any workaround or advice to do it better?
Edit: below is the initialization of the currenUserReference
currentUserReference = database.getReference("users/" + user.getUid());
the database is initialized in onCreate like that:
database = FirebaseDatabase.getInstance();
the "user" from which I get the Uid is the FirebaseUser that is returned by firebaseAuth.getCurrentUser after checking if the signInWithEmailAndPassword.addOnCompleteListener was successful.
and after the addListenerForSingleValueEvent I added a Log.d that is writtent as follows:
Log.d(TAG, "Value of variable: " + currentUserUsername);
I expect the currentUserUsername to have the value of "username" in the databased assigned, but instead, I get:
LoginActivity: Value of variable: null
Edit2: While debugging, I realized that when the debugger reaches the .addListenerForSingleValueEvent and I press F8, it completely skips this method. So, as someone said in the comment, the datasnapshot is probably not null, it's never accessed. But I still don't get why.
dataSnapshot.getValue(String.class) doesn't return null, when you exit onDataChange
your currentUserUsername is reinitialised.
So to pass the value to the next activity you need to:
currentUserReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
currentUserUsername = dataSnapshot.getValue(String.class);
// Get every information into a User class
MainActivity.CURRENT_USER_SESSION = new User(
currentUserUsername,
user.getEmail(),
user.getUid()
);
Intent intent = new Intent(
LoginActivity.this, MainActivity.class);
startActivity(intent);
}
I use addListenerForSingleValueEvent to add value into the child "code" of the current user, but instead the data duplicated.
Here is the database before the coding
This is the coding for the addListenerForSingleValueEvent
b4.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mAuth = FirebaseAuth.getInstance();
mUser = mAuth.getCurrentUser();
final String UserId = mUser.getUid();
mReference = FirebaseDatabase.getInstance().getReference("Users");
final DatabaseReference currentUserId = mReference.child(UserId);
currentUserId.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
user.setCode(riasec1);
currentUserId.child(UserId).setValue(user);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
});
Here is the database after the coding is executedWhy is the whole user being duplicated instead of only value "code" inserted?
When you call
currentUserId.child(UserId).setValue(user);
you are saying "add WHOLE user in to node with name 'UserId'".
In general you are fetching WHOLE User, updating one field and updating again.
So when would you like to update a child without rewriting the entire object u should pass node name, e.g.
currentUserId.child(UserId).child("code").setValue(riasec1);
In this call you say:
find node with proper ID (UserId)
find node "code"
set new value (riasec1)
I want that when a user seen message it should replaced text from delivered to seen. But this is happen only when i go back to and again come to that chat activity. My only issue is how to update "delivered" to "seen" when both users chat is open. All other working is fine except this issue.May be adapter not notifying.
here is my chat activity code for seen Messages:
private void seenMessage(){
messageRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (final DataSnapshot snapshot:dataSnapshot.getChildren()){
rootRef.child("Messages").child(friendUserId).child(currentUID).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot1) {
for (DataSnapshot snapshot1:dataSnapshot1.getChildren()){
ChatMessage chatMessage=snapshot1.getValue(ChatMessage.class);
if (chatMessage.getFrom().equals(friendUserId)){
HashMap seen=new HashMap();
seen.put("isseen",true);
snapshot.getRef().updateChildren(seen);
snapshot1.getRef().updateChildren(seen);
chatMessageAdapter.notifyDataSetChanged();
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
chatMessageAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
my Adapter code for this is here
if (userMessageList.size()-1==i){
if (message.isIsseen()){
chatMessageViewHolder.seenStatus.setText("Seen");
}
else {
chatMessageViewHolder.seenStatus.setText("Delivered");
}
}
else {
chatMessageViewHolder.seenStatus.setVisibility(View.GONE);
}
Replace addListenerForSingleValueEvent with addValueEventListener at your messageRef, since the first listener just fires one time to listen to your value at that reference, it will just notify to your adapter once, instead, the second one keeps listening if there is any change at that reference, and you will see the change at realtime without leaving the activity since the adapter will be notified whenever a change is made at that reference.
Each user in my app can send and get friend requests. When the user checks his friends requests, I want the app to go through each user who sent him a friend request and retrieve his information from the Realtime Database.
This is my code in order to accomplish this:
public void check_For_Friends_And_Requests(){
String loggedUser=SaveSharedPreference.getLoggedEmail(getApplicationContext());
final DatabaseReference mDatabase= FirebaseDatabase.getInstance().getReference();
final DatabaseReference userRef=mDatabase.child("Users").child(loggedUser);
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
final List<User> friendRequestsReceived_UserList=new ArrayList<>();
for (DataSnapshot postSnapshot: dataSnapshot.child("friend_requests_received").getChildren()) {
final String senderEmail=postSnapshot.getKey();
Toast.makeText(getApplicationContext(),
senderEmail, Toast.LENGTH_SHORT).show();
if (senderEmail!=null){
mDatabase.child("Users").child(senderEmail).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Toast.makeText(getApplicationContext(),
dataSnapshot.child("name").getValue(String.class), Toast.LENGTH_SHORT).show();
friendRequestsReceived_UserList.add(
new User(
senderEmail,
dataSnapshot.child("name").getValue(String.class),
dataSnapshot.child("level").getValue(Integer.class),
dataSnapshot.child("skill").getValue(Double.class)));
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
UserListAdapter friendRequestsReceived_Adapter =
new UserListAdapter(getApplicationContext(),
R.layout.friend_requests_received_listview_row,
friendRequestsReceived_UserList);
friendRequestsReceived_ListView.setAdapter(friendRequestsReceived_Adapter);
}
else
connectionErrorGoToMain();
}
#Override
public void onCancelled(DatabaseError databaseError) {
connectionErrorGoToMain();
}
});
}
I have in this code 2 ValueEventListeners. I add the user information to the list in the inner one. The problem is that the list is empty at the end of this process.
I would like to fill a list view with this information using these lines:
UserListAdapter friendRequestsReceived_Adapter =
new UserListAdapter(getApplicationContext(),
R.layout.friend_requests_received_listview_row,
friendRequestsReceived_UserList);
friendRequestsReceived_ListView.setAdapter(friendRequestsReceived_Adapter);
When I put them inside the innner listener, it works fine, but I don't want to set the adapter for each user in the list, only after the for loop.
I'm attaching a screenshot with my database structure (I don't need to get all of the parameters):
The list is empty because you are declaring friendRequestsReceived_UserList outside the inner onDataChange() method. This is happening due the asynchronous behaviour of onDataChange() method which is called before you are adding those new objects to the list. So, in order to solve this, just move the declaration of the list inside the inner onDataChange() method like this:
if (senderEmail!=null){
mDatabase.child("Users").child(senderEmail).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final List<User> friendRequestsReceived_UserList=new ArrayList<>(); //Moved here
Toast.makeText(getApplicationContext(), dataSnapshot.child("name").getValue(String.class), Toast.LENGTH_SHORT).show();
friendRequestsReceived_UserList.add(
new User(
senderEmail,
dataSnapshot.child("name").getValue(String.class),
dataSnapshot.child("level").getValue(Integer.class),
dataSnapshot.child("skill").getValue(Double.class)));
UserListAdapter friendRequestsReceived_Adapter =
new UserListAdapter(getApplicationContext(), R.layout.friend_requests_received_listview_row, friendRequestsReceived_UserList);
friendRequestsReceived_ListView.setAdapter(friendRequestsReceived_Adapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
As you probably see, i set the adapter also inside the inner method. If you want to use that list outside the onDataChange() i suggest you reading my answer from this post.