I am having some trouble knowing when my Firebase API call is finished. After reading the Firebase documentation I have found the following:
Value events are always triggered last and are guaranteed to contain updates from any other events which occurred before that snapshot was taken.
I understand this to mean that only after all the onChildAdded call is finished, then the ValueEventListener is called. As a result, I thought that I can populate my RecyclerView in the onChildAdded function and then the onSingleValueListener call, I can simply finish animating my loading screen (which has started animating before this function call) and proceed. However, I have run into an issue where I put some careful System.out.println statements and found that in my case, Test 1 is called before Test 2 is ever called. This causes problems because this is actually the opposite behavior of what I wanted: I wanted the onChildAdded function to finish and then call the onSingleValueListener function that prints out Test 1 to be called. Is there any reason why this is happening? Any way around this? I would appreciate an explanation on why this is happening. Thanks!
public void getComments(final String postId, final Activity activity, final View fragmentView, final View progressOverlay) {
final Firebase commentsRef = firebaseRef.child("/comments");
Firebase linkRef = firebaseRef.child("/posts/" + postId);
linkRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
System.out.println("Test 1");
if (progressOverlay.getVisibility() == View.VISIBLE) {
progressOverlay.setVisibility(View.GONE);
AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_view_comments).setVisibility(View.VISIBLE);
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
linkRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
commentsRef.child(dataSnapshot.getKey()).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Comment comment = dataSnapshot.getValue(Comment.class);
System.out.println("Test 2");
application.getCommentsRecyclerViewAdapter().getCommentsList().add(comment);
application.getCommentsRecyclerViewAdapter().notifyDataSetChanged();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
You may want to use the **FirebaseRecyclerAdapter** class that the Firebase team makes available in FirebaseUI-Android (see https://github.com/firebase/FirebaseUI-Android/blob/master/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java)
In your gradle file add the line below (check here for latest version number in the readme)
compile 'com.firebaseui:firebase-ui-database:0.4.3'
With this code "Firebase linkRef = firebaseRef.child("/posts/" + postId);" I could see that you're using legacy Firebase API. Its deprecated now!
Kindly update your code to new Firebase 3.x.x API.
Below two are independent async call; Based on your use-case, you can use either one of the listener to read your data.
1. linkRef.addListenerForSingleValueEvent(new ValueEventListener() {});
2. linkRef.addChildEventListener(new ChildEventListener() {});
You can refer the firebase document to get more information about database listeners.
https://firebase.google.com/docs/database/android/retrieve-data
With the following code snippet, you can retrieve and populate your list of comments.
public void getComments(final String postId, final Activity activity, final View fragmentView, final View progressOverlay) {
DatabaseReference commentsRef = firebaseRef.child("/comments");
DatabaseReference linkRef = commentsRef.child("/posts/" + postId);
linkRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Iterate through data-snapshot, and update your Adapter dataset
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Comment comment = snapshot.getValue(Comment.class);
application.getCommentsRecyclerViewAdapter().getCommentsList().add(comment);
}
application.getCommentsRecyclerViewAdapter().notifyDataSetChanged();
// Dismiss your loading progressbar
if (progressOverlay.getVisibility() == View.VISIBLE) {
progressOverlay.setVisibility(View.GONE);
AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_view_comments).setVisibility(View.VISIBLE);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Handle fail case here
}
});
Hope this would help you!
Related
I am trying to delete an image and some data from firebase and remove these from my recyclerview.
I have successfully managed to accomplish this and it's deleting the image allright, but it's not instantly deleting the image in the interface.
I have to leave the activity and return in order to view the updated view.
this is my ChildEventListener:
mDBListener = mDatabaseRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
mUploads.clear();
for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
Upload upload = postSnapshot.getValue(Upload.class);
upload.setKey(postSnapshot.getKey());
mUploads.add(upload);
}
mAdapter.notifyDataSetChanged();
mProgressCircle.setVisibility(View.INVISIBLE);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
mAdapter.notifyDataSetChanged();
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
what happens when i press delete:
#Override
public void onDeleteClick(int position) {
final Upload selectedITem = mUploads.get(position);
final String selectedKey = selectedITem.getKey();
Log.d(TAG, "SelectedKEY" + selectedKey);
StorageReference imageref = mStorage.getReferenceFromUrl(selectedITem.getImageUrl());
imageref.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if(user != null) {
Intent intent = getIntent();
Bundle bd = intent.getExtras();
String selectedId = (String) bd.get("selectedId");
mDatabaseRef.child(selectedId).child(selectedKey).removeValue();
Toast.makeText(ImagesActivity.this, "Item deleted ", Toast.LENGTH_SHORT).show();
}
}
});
}
and the onDestroy override:
#Override
protected void onDestroy() {
super.onDestroy();
mDatabaseRef.removeEventListener(mDBListener);
}
}
UPDATED TRY FOR SOLUTION
I tried for a solution and got a little bit closer to my goal.
Now when I delete an item in the view, and call for a Toast in my onChildRemoved, I get the toast. This is the code:
DatabaseReference mDatabaseRefff = FirebaseDatabase.getInstance().getReference(Uid).child(selectedId).child(selectedId).child(key);
dbEvent = mDatabaseRefff.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Toast.makeText(ImagesActivity.this, "Item deleted ", Toast.LENGTH_SHORT).show();
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
so basically I did not remove the old childeventlistener (Because it worked perfectly as it was supposed to work for everything else)
And now i put this childEventlistener below the first one, only now I am listening on the same child as I am trying to remove.
Now when I delete an item from the view, I get the Toast "Item deleted".
The problem is that when I add mAdapter.notifyDataSetChanged();
It still only shows the toast and nothing else happens...
I believe you need to Override onChildRemoved, remove the item from the adapter and call notifyDataSetChanged() to refresh the recyclerView.
onChildRemoved() Listen for items being removed from a list. The DataSnapshot passed to
the event callback contains the data for the removed child.
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
//add logic to remove item from adapter
String key = dataSnapshot.getKey();
//mAdapter.remove(index);
mAdapter.notifyDataSetChanged();
}
Following the update on the question, the next thing to do is simply update the Adapter, you detect what was removed from the database through the onChildRemove() and remove that from your adapter. Once you remove from the adapter, you simply call notifyDataSetChanged() and should work.
More info about Firebase Listen for child events
I didn't manage to find a solution in the childEventListener method. So I used valueEventListener instead and solved the issue.
I have an android project where I retrieve data from firebase. everything is working fine but when I delete object in firebase console, it does not reflect back in the app.
Here is the link of the image:
So, suppose I delete david hafiz child node in firebase, it does not delete in the app. I have tried a lot but can't find the correct way. I am new to android programming and I hope somebody can help me. Thank You.
Update
mDatabase = FirebaseDatabase.getInstance();
mReference = mDatabase.getReference().child("Students").child("Marks");
mReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d("value", "" + dataSnapshot);
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return view;
}
private void fetchData(DataSnapshot dataSnapshot) {
StudentData value = dataSnapshot.getValue(StudentData.class);
Log.v("StudentData Fragment", "" + dataSnapshot.getValue());
// Get an iterator.
Iterator<StudentData> ite = mMarksList.iterator();
while (ite.hasNext()) {
StudentData iteValue = ite.next();
if (iteValue.equals(value))
ite.remove();
}
mMarksList.add(value);
Collections.sort(mMarksList, new MarksComparator());
String title = mReference.getKey();
// specify an adapter
mAdapter = new MyAdapter(getContext(), mMarksList, title);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);
}
There are four relevant methods in ``:
onChildAdded
onChildRemoved
onChildChanged
onChildMoved
You only implemented onChildAdded, which is called in two cases:
When you first attach the listener, onChildAdded is called for each existing child.
When a child is later added, onChildAdded is called for that child.
When you delete a node from the Firebase console, the onChildRemoved method is called. But since you left that method empty, your app doesn't do anything when you remove data from the console.
To make your app behave correctly, you'll need to implement onChildRemoved. Typically this involves finding the UI element matching the snapshot and removing it.
You can store keys in order to update or remove it e.g:
ArrayList<String> mKeys = new ArrayList<String>();
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String key = dataSnapshot.getKey();
mKeys.add(key);
adapter.notifyDataSetChanged();
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
String key = dataSnapshot.getKey();
int index = mKeys.indexOf(key);
mMarksList.remove(/*index*/); // or data
adapter.notifyDataSetChanged();
}
You might be interested how to update too.
I am working on android app in which I want to store unique IDs of users in firebase database, so to achieve this I have created a root, named "Users" and will store all the users unique IDs in this root.
So in order to check uniqueness I have to go through each child of "Users" and after checking the uniqueness I will add the uniqueID to "Users" root or will notify the user that uniqueId is not available.
When I check uniqueness, it's conditionals (if/else) execute before occurrence of OnChildAdded event, How to execute some code(mentioned in my code) after occurrence of OnChildAdded event
My Code
postBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!uniqueIdText.getText().toString().trim().equals("")) {
uniqueness = true;
users.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot.getKey().equals(uniqueIdText.getText().toString().trim()))
uniqueness = false;
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
else if(uniqueIdText.getText().toString().trim().equals("") )
Toast.makeText(LoginActivity.this,"Please enter id",Toast.LENGTH_LONG).show();
//======================How to execute it(code below) after occurrence of OnChildAdded event====================
if (uniqueness){
DatabaseReference usersInfo = users.child(uniqueIdText.getText().toString().trim());
startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
else{
if(!uniqueIdText.getText().toString().trim().equals(""))
Toast.makeText(LoginActivity.this, "ID not available, Please enter a new one", Toast.LENGTH_SHORT).show();
}
}
});
There is no way to know when all the onChildAdded events have been called. But you can know when the first data from the database has been synchronized by using a ValueEventListener.
But in this case, I'd highly recommend taking a different approach. Downloading all UIDs to check if one value is present is incredibly inefficient and won't scale. Instead, attach a listener to the specific UID that you're looking for. If that value exists, you'll get the value. If it doesn't exist, you'll get an empty snapshot:
users.child(uniqueIdText.getText().toString()).addSingleValueEventListener(#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
uniqueness = false;
}
else {
uniqueness = true;
}
...
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "onCancelled", databaseError.toException());
// ...
}
};
My structure is like following
- Posts
- keu23jndf
- dateTime:"2376464"
- name:"abc"
- key34njf
- dateTime:"4333434"
- name:"xyz"
Now, I want to retrieve sorted data by dateTime in descending order.
Thanks
Ok so first of all I would suggest you to not use a client sided timestamp but instead use the ServerValue.TIMESTAMP. In most cases this is going to be the better setup. If you know what you are doing and you want it exactly like that, forget this and just read on.
The problem if you want to sort from newest to oldest is that Firebase does (as far as I know) not support this.
A workaround would be to use the negative value of the Firebase ServerValue.TIMESTAMP.
This is how I do it. It may not be the perfect or right way, but it does work
private void prepareUpload() {
//mDatabase is a reference to the root of the Firebase Database
final DatabaseReference timestampReference = mDatabase.child("timestamp");
final String timestampKey = timestampReference.push().getKey();
timestampReference.child(timestampKey).setValue(ServerValue.TIMESTAMP);
timestampReference.child(timestampKey).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.getValue() != null) {
Long timestamp = 0 - Long.parseLong(dataSnapshot.getValue().toString());
upload(/*Starting upload with new timestamp here (this is just a dummy method)*/);
timestampReference.child(timestampKey).removeValue();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, databaseError.getMessage());
}
});
}
Now you have to set your Firebase Database rules as follows:
"Posts": {
".indexOn" : "date",
...
}
Finally you just have to query your 'Posts' somehow like this:
mDatabase.child("Posts").orderByChild("dateTime").addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
//Do stuff with your data
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope this helps!
I am working in my app with Firebase and I tried to use onChildAdded() callback but I think I am doing something wrong because I get no response.
/**
* Constructor
*/
private FirebaseManager(Context context) {
Firebase.setAndroidContext(context);
Firebase.getDefaultConfig().setPersistenceEnabled(true);
mFirebaseRef = new Firebase(FIREBASE_URL);
}
public void checkUsers() {
Firebase checkRef = mFirebaseRef.child("/data/users/");
// Also tried
// Firebase checkRef = mFirebaseRef.child("data").child("users");
checkRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d(TAG,"New child added");
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
In onCreate() of my MainActivity I authenticate the user and the call checkUsers(). After that I manually add a new user in database. But I get nothing in log.
This is the json structure:
What am I doing wrong?
I got it. There was a concurrency problem. I called checkUsers() in onCreate() of the MainActivity but it needs to be called in onAuthenticated(), because the user was authenticated after the method checkUsers() tried to set listeners.
I got a LOG about the failure of accessing database:
W/SyncTree: Listen at / failed: FirebaseError: Permission denied
but didn't seen it because I was following a specific TAG with logcat Search.