I have a ListView that each row contains several EditText and a save Button.
In the ShowOrder activty i'm getting the data from Firebase
public void getItemsOrderDetails(final String key){
orderDetailsRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
arrayLength = dataSnapshot.getChildrenCount();
String itemN="", discription="", qty="", pcsCtn="", ctnWt="", compileStatus="", palletN="", ttlCtn="";
if(!key.equals("costumerName") && !key.equals("dateOfDelivery") && !key.equals("dateOfOrder") && !key.equals("orderN") && !key.equals("remarks") && !key.equals("status")){
if(dataSnapshot.child(key).hasChild("itemNumber")){
itemN= dataSnapshot.child(key).child("itemNumber").getValue().toString();
}
...
... {downloading all data}
...
itemsClass= new ItemsClass(orderNumber, itemN, discription, pcsCtn, ttlCtn, ctnWt, qty, compileStatus, palletN );
itemsClassArrayList.add(itemsClass);
adapter.notifyDataSetChanged();
}
I'm showing all the data at the adapter , and problem is when I want to update the data onChildAdded is beeing triggered multiple times.
This is the update part in the Adapter:
private void updateData(ViewHolder vh, String orderN){
showOrder.itemsClassArrayList.clear();
updateStatus="idle";
ordersRef.child(orderNumber).child(itemN).child("palletNo").setValue(pallete);
ordersRef.child(orderNumber).child(itemN).child("pcsPizza").setValue(untCtn);
ordersRef.child(orderNumber).child(itemN).child("compileStatus").setValue(collected);
ordersRef.child(orderNumber).child(itemN).child("drink").setValue(ctnWt);
ordersRef.child(orderNumber).child(itemN).child("ttlWt").setValue(pltWt);
ordersRef.child(orderNumber).child(itemN).child("ttl").setValue(ttlCtn);
updateStatus= "update";
//showOrder.adapter.clear();
}
What I discovered is that if i'm updating only one child (Deleting all the others) the onChildAdded will be update only once.
So i don't understand how can i update all with out multiple Updating, if anyone as an idea.
Thank you
This might not be a complete solution to your issue, but a few things struck me right away:
You can (and should) load and store complete Objects in your database, so instead of reading and writing each child (e.g. of your ItemsClass object) separately, you should store your item like so:
ordersRef.child(orderNumber).child(itemN).setValue(myItem)
where myItem is an instance of ItemsClass that has been populated with data before.
In a similar way, loading data should be done like so (depending on where orderDetailsRef points to! see below):
public void onDataChange(DataSnapshot dataSnapshot) {
ItemsClass items = dataSnapshot.getValue(ItemsClass.class);
}
Also, you mention onChildAdded getting called multiple times, yet in your example you are using a ValueEventListener's onDataChange method.
Could you give a complete code sample? The one you posted is lacking relevant parts of your solution. And let me know how your code behaves when you load and save the way I suggested.
Related
I am designing a simple basic chat app using firebase real time database and i've designed everything well, however, i'm facing one sllight issue. My chats keep duplicating themselves on the inbox page (the page whrere the chats are laid out for a user to select which chat he wants to open and start talking).
I've attached an image of what i mean below.
Screenshot of the phone screen
The code i am using to get the chats and display them in the recycler view is given below. I have a directory called Conversations in my DB that saves a user's Id and under it, theres a child of each and every person he chats wit, under which is the last message and a seen boolean.
Database Structure
The code is given below
convoref = FirebaseDatabase.getInstance().getReference().child("Conversations").child(currentUid);
and then..
public void getConvoIds() {
convoref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for(DataSnapshot convo : dataSnapshot.getChildren()){
boolean isMessageSeen = false;
String lastMessage = "";
if(convo.child("seen").getValue() != null) {
isMessageSeen = (boolean) convo.child("seen").getValue();
}else{
Log.i("nolastseen", "location is null");
}
if(convo.child("lastMessage").getValue() != null) {
lastMessage = convo.child("lastMessage").getValue().toString();
}else{
Log.i("nolastMessage", "location is null");
}
Log.i ("the_convo_partner_key", convo.getKey());
Log.i ("lastseenmessage", lastMessage);
Log.i ("seenstate", String.valueOf(isMessageSeen));
FetchConvoInfo(convo.getKey(), isMessageSeen, lastMessage );
}
}
}
the fetch convo information functuion is below
public void FetchConvoInfo(final String key, final boolean isMessageSeen, final String lastMessage){
FirebaseDatabase.getInstance().getReference().child("Users").child(key).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean chatExists = false;
String username = "";
String thumbnail = "";
String chatPartner;
chatPartner = key;
if(dataSnapshot.child("username").exists()){
username = dataSnapshot.child("username").getValue().toString();
}
if(dataSnapshot.child("thumbnail").exists()){
thumbnail = dataSnapshot.child("thumbnail").getValue().toString();
}
ConvoClass obj= new ConvoClass(chatPartner, username, thumbnail, isMessageSeen, lastMessage);
resultConvos.add(obj);
mConvoAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Any help would be greatly appreciated. i cant seem to figure out why the chat duplicates.
In your onDataChanged method, you are going through every child of the dataSnapshot. Each child of the data snapshot indicates a particular conversation of that particular currentUid guy. So when you are going through every child of the dataSnapshot you are adding all its children to the listview or recyclerview(I don't know what you are using. But you are adding it to the adapter). So you are adding the old data again and again whenever some new data must be added. Hence-duplicate data.
There are two common solutions.
The first is naive method. Do what you are doing right now. But while adding an item (chat, you will call it in your case, I think) to the adapter, check if it is already present in the container resultConvos. That will prevent you from adding duplicate chats. I am sure it is obvious to you also why this method is inefficient. You are unnecessarily having to go through every conversation of a person. It takes O(n) time for just adding one item.
The second method is the recommended method. Remove all the code of ValueEventListener. Instead use ChildEventListener. I don't know if you are aware of it. Check this.
ChildEventListener has mainly 4 methods instead of onDataChanged. Among that, what you require here is onChildAdded. Just like your onDataChanged, it has one argument- a data snapshot. But this data snapshot contains only the newly added child, whereas the data snapshot in onDataChanged contains the whole data of the conversations of that particular user (that means the whole list). So using the data snapshot provided by onChildAdded you can directly add only that chat to the adapter, which takes O(1) time.
For more about ChildEventListener, read that link I attached
I fetch data that is constantly changing from Firebase realtime database.
Then I check if an item exists in an ArrayList, I don't add it, but if it does, I add and notify that the item was inserted. But, since the database is constantly changing, the entire list is "blinking". How can I add remove items inside the database listener, without making it blink? i.e only add/remove the specific item that is changing. This is the code now:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot datas: dataSnapshot.getChildren()){
if (x<token){
if (!myDataset.contains(datas.getKey().toString())){
int position = myDataset.indexOf(datas.getKey().toString());
myDataset.add(datas.getKey().toString());
mAdapter.notifyItemInserted(myDataset.size() - 1);
}
} else {
myDataset.remove(datas.getKey().toString());
int position = myDataset.indexOf(datas.getKey().toString());
mAdapter.notifyItemRemoved(position);
}
}
Your current code notifies the adapter of each individual item in the list, whenever there are any changes. The smallest possible change that will reduce the blinking is to move the notifying of the adapter outside of your loop:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bool isChanged = false;
for(DataSnapshot datas: dataSnapshot.getChildren()){
if (x<token){
if (!myDataset.contains(datas.getKey().toString())){
int position = myDataset.indexOf(datas.getKey().toString());
myDataset.add(datas.getKey().toString());
isChanged = true;
}
} else {
myDataset.remove(datas.getKey().toString());
int position = myDataset.indexOf(datas.getKey().toString());
isChanged = true;
}
if (isChanged) {
mAdapter.notifyDataSetChanged()
}
}
}
Now for any update to the values, you will only notify the adapter (and hence force it to repaint) only once.
I'd recommend a different approach though. Since you are interested in additions and removal of child nodes, it is likely more efficient to use a ChildEventListener. This listener has callback methods for specific changes, such as onChildAdded, and onChildRemoved, that get called when a child is added or removed respectively. These callbacks get the precise information you need t update your myDataset.
You should still also use a ValueEventListener to detect when all changes have been received, and user that to notify the adapter of the changes, so that it rerenders the view. Since ValueEventListener is guaranteed to fire after the corresponding ChildEventListener callbacks, this sequence will work.
So:
Use a ChildEventListener to listen to more granular changes, to allow direct updates of the myDataset without having to loop and detect it yourself.
Use a ValueEventListener to detect when the child-level changes are done, and notify the adapter that it needs to repaint the view.
For a small example of this approach in production code, see the FirebaseArray class in the FirebaseUI library.
I have several strings stored under specific reference: mReference.child(rID).child(userID2) which I want to retrieve using childEventListener as I am doing some task also when these string gets removed and that is only possible with onChildRemoved of ChildEventListener.
Here's what I have tried:
mReference.child(rID).child(userID2).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d("dataHEre", dataSnapshot.getValue().toString());
}
}
The problem is that I'm unable to retrieve data using keys here as dataHEre is logging out:
D/dataHEre: 28.8419556
D/dataHEre: 78.779063
D/dataHEre: 3
D/dataHEre: Railway Colony
D/dataHEre: Sleep
D/dataHEre: 12:36 AM
D/dataHEre: Superman
which are values?
So, I want to know how can I retrieve data here using keys and using ChildEventListener and then assign the data retrieved to various strings?
I think that you are doing your query in the wrong way. onChildAdded method is retrieving you each child of a specific value (userID2 I suppose). If that is what you want, then just use a onValueEventListener() to retrieve the whole dataSnapshot of your userId2 node each time that it changes.
If you want to retrieve the data just once, you should use onSingleValueEventListener(). And OnChildEvent() is used to retrieve lists of data where you want to track each of the child individually. For example if you attach an OnChildEventListener to mReference.child(rID) you will get a OnChildAdded for each userId, what is pretty usefull like for example fill a RecyclerView or a ListView being able to update each item individually together with your Firebase Data.
If i'm not wrong what you want is just get updates of your userId2 reference, in that case attach a OnValueEventListener to that reference and you will get a call each time a value is modified, deleted, or added.
firebaseDatabaseRef.addValueEventListener(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
customPOJO customPOJO = dataSnapshot.getValue(YourCustomPOJO.class);
customPOJO.getName();
customPOJO.getEmail();
customPOJO.getfavoriteFood();
//and so on....
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});
I've been trying to make this work from
How to delete firebase data after "n" days
but it's not working for me.
Here's what I'm doing,
In my "A" activity, I have a button that will save this chunk of data, with a 'timeStamp' child which holds the timestamp value.
(.setValue(ServerValue.TIMESTAMP);)
After pressing the button, it saves the data successfully. Then, it starts the next activity, where we wait.
But instead of deleting after '30' days, it deletes it straight away.
I have a method that works exactly like the answer by Frank
long cutoff = new Date().getTime() - TimeUnit.MILLISECONDS.convert(30, TimeUnit.DAYS);
Query oldBug = mDatabase.orderByChild("timeStamp").endAt(cutoff);
oldBug.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot itemSnapshot: snapshot.getChildren()) {
itemSnapshot.getRef().removeValue();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
But it's not deleting it after some time, as soon as it is posted.
Thank you.
EDIT:
The orderByChild() sorting method is very forgiving. The children being sorted are not required to have a member with the specified field name. The documentation explains that those children are assigned a null value and appear first in the sort. Thus, if the reference used to create a query is incorrectly located, the query doesn't fail and instead will typically return all the children of that location.
You created your oldBug query using mDatabase where:
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
This is one level too high. It should be:
Query oldBug = mDatabase.child("Users").orderByChild("timeStamp").endAt(cutoff);
I think you might consider making a new child item that has a timestamp and date so that you can reference back to it when doing a query for the date and time. This way you don't have to worry about incorrect values and you can ensure you are deleting the correct data. I hope this helps.
I'm currently developing an Android app that deals with storing book translations. I'm using Firebase to hold all the data online, and I have no trouble pulling data from it and storing them as Book objects. I have a master list of books in the main class that stores all the books from the Firebase database, however the list doesn't seem to update properly.
public static List<Book> books = new ArrayList<Book>();
protected void onCreate(Bundle savedInstanceState) {
myFirebaseRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot bookSnapshhot : snapshot.getChildren()) {
Book book = bookSnapshhot.getValue(Book.class);
books.add(book);
System.out.println(book.getAuthor() + " - " + book.getTitle());
for (Chapter chapter : book.getChapters()) {
System.out.println(chapter.getTitle());
}
for (Genre genre : book.getGenres()) {
System.out.println(genre.getGenre());
}
System.out.println(books.size());
}
}
#Override public void onCancelled(FirebaseError error) { }
});
System.out.println(books.size());
}
In that body, the size of books is correct (right now it is only 1). However if I call the same print statement outside of that code body (the last line), it gives me 0. I don't know why, because it successfully adds it inside the onDataChange() method but it seems to not apply the changes on the outside.
I think that your problem is about the asyncronously of the ValueEventListener.
The onDataChange is called asynconously.
You have to use the books after the onDataChange is called.
The addValueEventListener() will fetch the data from Firebase asynchronously.
What your code depicts is that you are adding ValueEventListener and then printing System.out.println(books.size());. This means your logs gets displayed after you add ValueEventListener(which doesn't means your listener gets invoked). At this point your listener is not invoked yet and so isn't onDataChange(). Thus you won't ever get updated count outside the listener scope.