Firebase recyclerAdapter duplicate objects into arrayList - android

final FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter(Service.class, R.layout.browse_service_detail, ServiceHolder.class, mReference){
#Override
protected void populateViewHolder(ServiceHolder serviceHolder, Service service, int position) {
serviceHolder.setServiceName(service.getName());
serviceHolder.setInfo("От " + service.getPrice1());
service.setQuantitySelected(service.getQuantityEnabled());
if (Order.getInstance().getServices() != null) {
for (Service serviceFromSingleton : Order.getInstance().getServices()) {
if (serviceFromSingleton.getName() == serviceHolder.getServiceName().getText().toString()) {
serviceHolder.getServiceName().setSelected(true);
serviceHolder.getServiceName().setTextColor(getResources().getColor(R.color.yellow));
}
}
}
//add item to array
servicesList.add(service);
}
}
};
When I run this activity, it records the visible list objects to an array, but when I scroll down and go back up, it duplicates the first elements again into the array. How to fix it? For an item to be added only once.

I don't think there is any issue in RecyclerAdapter..I think the list only inserting same data multiple times.
why not you check whether the list is empty or not before adding data into it and clear the data if its not empty and then add new.
if(servicesList.isEmpty())
servicesList.add(service);
//else clear and add data
else{
servicesList.clear();
servicesList.add(service);
}
To handle data duplicacy, you can use a Set which will ignore duplicate inserts on scrolling.
servicesList.add(service);
Set<Service> mSet= new HashSet<Service>();
mSet.addAll(servicesList);
servicesList.clear();
servicesList.addAll(mSet);
OR use Set other than ArrayList
little clumsy but will work for you.

Related

Android - Add new Items to MutableLiveData and observe

How can I add new Items to a MutableLiveData List? I want to build an infinite scrolling Recyclerview. So I have a List with 10 items and after clicking a "load more button" I want to add ten new Items.
This are my lists of products:
private companion object {
private var _products = MutableLiveData<MutableList<Product>>()
}
val products: LiveData<MutableList<Product>> = _products
Ant this is my loading function:
fun loadProducts(category: Category) { // category: String
_isViewLoading.postValue(true)
AppService.fetchProducts(category, neuheitenCounter) { success, response ->
_isViewLoading.postValue(false)
if(success) {
when(category) {
Category.NEWS -> {
if(_products.value == null) {
_products.value = response?.products //as List<Product>
} else {
response?.let {
_products.value?.addAll(response?.products)
}
}
neuheitenCounter++
}
}
}
}
}
If I call _products.value = response?.products it fires the observe method in my Activity. But if I call addAll or add, the observer method is not called.
Actually, it's how you should update the adapter
Add new items to the existing list
Notify LiveData by assigning the same list to its value. Here is the same question with the solution.
When your Activity is notified that the list is changed, it should notify the adapter about the changes by calling its notifyDataSetChanged() method.
When you call notifyDataSetChanged() method of the adapter, it compares previous items with the new ones, figures out which of them are changed and updates the RecyclerView. You can help the adapter by calling notifyItemRangeInserted() instead of notifyDataSetChanged(). notifyItemRangeInserted() accepts a range on inserted items, in your case you can calculate it but taking the difference between adapter.itemCount and the productList.size.
MutableLiveData.postValue(T value)
I had that same issue .I copied data of LiveData in new List then i added new data to new List.And assigned new List to LiveData.I know this not exact SOLUTION but this solved problem.
Try this.
var tempList= _products.value
tempList.addAll(response?.products)
_products.value = tempList
I hope this will help you.

Sorting infinite scrolling list items implemented using Paging library

Let's say I have implemented an infinite scrolling list using Paging Library, but now I want to give the user an option to sort the data at client side, how can I do it? For example, I have movies data, the Paging library is working fine to load all movies, but how can I sort the movies based on say rating or release date.
Any help would be appreciated.
I haven't tried this method but hope it works .
Assuming you use RecyclerView to show the list (because it is much easier in recyclerView)
When you get the first ten item from server and you choose to sort it.Lets say by name(ascending) .You have to use two ArrayList here:
1:finalArrayList :- which contains all the whole list
2:updateArrayList :- which has the latest ten result
when you choose to sort (onButtonClick)
updateArrayList = new ArrayList<>();
updateArrayList.addAll(finnalArrayList);
Collections.sort(updateArrayList, new Comparator<StoreModel>() {
public int compare(StoreModel obj1, StoreModel obj2) {
// ## Ascending order
if (sortingBy == 0) {
return obj1.getName().compareToIgnoreCase(obj2.getName());
}});
adapter.clear();
adapter.addAll(updateArrayList);
Run this code only when the button is clicked
Now when you scroll down and more data comes
updateArrayList = new ArrayList<>();
updateArrayList.add(StoreModel)//Add the latest ten data from the response
and sort it before sending it to adapter
Collections.sort(updateArrayList, new Comparator<StoreModel>() {
public int compare(StoreModel obj1, StoreModel obj2) {
// ## Ascending order
if (sortingBy == 0) {
return obj1.getName().compareToIgnoreCase(obj2.getName());
}});
adapter.addAll(updateArrayList);
hope you got it and hope it works.Cheers:)

Firebase onChildAdded is being triggered multiple times

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.

Using cache policy "CACHE_THEN_NETWORK" adds duplicate rows in the Listview

I am using parse query to get some data and show it int he list view. I am using cache policy ParseQuery.CachePolicy.CACHE_THEN_NETWORK which fetches the data from cache first,then shows it in the list view, then fetches the data from the network and shows the updated data in the listView.
The problem is, when the data is fetched from the network (after fetch from cache) then duplicate rows are added to my listView. I can not simply clear the list in the adapter because i am using pagination in the listView.
My question is, is there any callback which triggers when the data is fetched from cache or network? or is there any other way round to fix the described problem?
When you are adding items to your list, remove the item that is already there.
public void addItem(YourItem item){
for(YourItem item2 : yourItemsList){
if(item.getObjectId().equals(item2.getObjectId()){
yourItemsList.remove(item2);
break;
}
}
yourItemsList.add(item);
yourAdapter.notifyDataSetChanged();
}
Better yet, if you are subclassing ParseObject you can override equals() in your class:
#ParseClassName("YourItem")
public class YourItem extends ParseObject {
/* All your accessors */
#Override
public boolean equals(Object o) {
return o != null
&& o instanceof YourItem
&& ((YourItem) o).getObjectId().equals(getObjectId());
}
}
Then, you can change the addItem method to this:
public void addItem(YourItem item){
yourItems.remove(item); //this will remove the object with matching ID if it exits.
yourItems.add(item);
yourAdapter.notifyDataSetChanged();
}

Pager with RecyclerViews containing realmObjects keeps crashing with IllegalState

I have a project using Realm.io for storing entities, this used to work fine, but now I have a fragment or activity containing 3 fragments with Lists of Realm objects.
Whenever I Switched to a page and back to the first one (or whatever just returning to a page). I get the java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
This seems to occur because the objects are no longer valid. Is there a simple way to detach them or something? Allthough it would be nice if they remain managed since I sometimes want to be able to delete them.
The items are queried from database, when there are not sufficient items they will get loaded from the API. Nothing extremely fancy is being used here, three lists with adapters which load the entities. THe difference per list is a string value status, which says if it's an certain status.
I get the error when I load the item from the Adapter after clicking the list item to show the details:
MyEntity myEntity = (MyEntity) adapter.getItem(position);
intent.putExtra("id", myEntity.getId()) <-- this part will crash it.
with exception:
java.lang.IllegalStateException: Illegal State: Row/Object is no longer valid to operate on. Was it deleted?
I guess it's because it's querying the same type of data on three locations (3 tabs). Though I would expect this not to be a problem since they all have their own adapter and list of items. Fetched from their own instances.
This is the code being called by my "Loader" class which handles the from DB and/or Api fetching.
public void loadResult(List result, boolean isFinished) {
//not the best for speed, but at a max of 10 items this is fine to not get duplicates and respect the original order
try {
for (RealmObject ro : result) {
Record r = (Record) ro;
int itemIndex = items.indexOf(r);
if (itemIndex > -1) {
items.set(itemIndex, r);
} else {
items.add(r);
}
}
} catch (IllegalStateException e) {
ErrorClass.log(e);
}
notifyDataSetChanged();
setLoading(!isFinished);
end = result.size() < 10 && isFinished;
}
in short the loader class does this, and it's not a singleton, it's a new instance per Listview (Recycler)
List result = null;
if (sortKey != null) {
result = query.findAllSorted(sortKey, ascending);
} else {
result = query.findAll();
}
if (result.size() < PAGE_SIZE && retry == 0) {
isFinished = false;
retry++;
getPageFromApi(pageNumber);
} else if (retry > 0) {
retry = 0;
}
adapter.loadResult(result, isFinished);
The getPageFromApi will result on this code being called again, and existing entities will be replaced in the list, new items added. So no old removed items should exist in the list when clicking them.
I think this might be very specific but there must be a global reason/solution to my problem.
Stupid me, I wrapped the adding of the new elements in a try catch because of the error before, what was going wrong is pretty simple. In the Loader the items fetched from our API was updating or creating new items. Meaning that those in the list, will be invalid at that point, or at least the pointers to them? Not sure how it works behind the scenes.
What I did to fix it, was loop through all the current items, and check the isValid(), if false the item would be removed. Otherwise I was checking for a new item to be inside the current items List, which would cause the error to occur in the .equals function!
This one thing is something that might be a core error, but I think it's just my error!

Categories

Resources