notifydataSetChanged on Adapter will update with new items, but will not update the existing items
android, java
Solution
There is a similar issue here with a solution that may work:
ListView not refreshing already-visible items
Problem
I could not find something specifically relating to my exact issue, please read on to find out what that is. I took great care to make sure that everywhere in my code, I am set up right to just call notifyDataSetChanged on the adapter, I initialize the itemList once, and pass that to the adapter, and don't re-initialize it ever. It works like a charm, and the list view will update itself, but only for new items. For existing items, the ListView will not update correctly. For example if I have a listview that is displaying some custom items, and I need to update it I do this ``` public void updateList(List<item> newItems) { if (adapter == null) { itemList.addAll(newItems); adapter = new SomeAdapter(layoutInflator, itemList); listView.setAdapter(adapter); } else { // lets find all the duplicates and do all the updating List<item> nonDuplicateItems = new ArrayList<item>(); for (Item newItem : newItems) { boolean isDuplicate = false; for (Item oldItem : itemList) { // are these the same item? if (newItem.id == oldItem.id) { isDuplicate = true; // update the item olditem.text1 = newItem.text1; oldItem.text2 = newItem.text2; } } if (isDuplicate == false) { // add the new item nonDuplicateItems.add(newItem); } } // I have tried just adding these new ones to itemList, // but that doesnt seem to make the listview update the // views for the old ones, so I thought thuis might help // by clearing, merging, and then adding back nonDuplicateItems.addAll(itemList); itemList.clear(); itemList.addAll(nonDuplicateItems); // finally notify the adapter/listview adapter.notifyDataSetChanged(); } } ``` now the listview will always update to show new items, but it will not update the views on the existing items. Here is the real kicker that tells me it is an issue with the views: if I call `adapter.getItem(position);` on a updated pre-existing item, the item returned will show the updated changes, (meaning text1 and text2 will hold their new values) even though it is not reflected in the listview! If I call `listView.invalidateViews();` then the list view will show the updates, but I have two problems with that, sometimes it flickers, and sometimes, just sometimes if I call it and it runs before the `notifyDataSetChanged` can finish getting through to the listview, I get a "List view not notified of data change" error! Does anyone know anything about this? ``` @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = layoutInflator.inflate(R.layout.item_comment, null); // when the holder is created it will find the child views // it will then call refreshHolder() on itself viewHolder = new ViewHolder(convertView, position); convertView.setTag(viewHolder); } else { viewHolder = ((ViewHolder) convertView.getTag()); viewHolder.refreshHolder(position); } return convertView; } public void refreshHolder(int position) { this.position = position; tvText1.setText(getItem(position).text1); tvText2.setText(getItem(position).text2); } ``` I wonder if what I should do is re-instantiate all my items before adding the to the list, using a copy constructor. Perhaps when notifying the adapter, the adapter will assume there is no changes if the `item` is still the same reference, and so will not redraw that view? or perhaps the adapter only draws new views for new items when notified? To add another detail, if I scroll down making the updated view go off screen, and then come back to it, it displays the correct info as the listview refreshes/remakes that view. I guess I am needing the listview to refresh all its current views so, `invalidateViews();` may be what I have to do. Does anyone know more about this? EDIT: As requested here is an adapter that would have this issue. ``` public class ItemAdapter extends BaseAdapter { private final static int VIEWTYPE_PIC = 1; private final static int VIEWTYPE_NOPIC = 0; public List<Item> items; LayoutInflater layoutInflator; ActivityMain activity; public ItemAdapter(List<Item> items, LayoutInflater layoutInflator, ActivityMain activity) { super(); this.items = new ArrayList<Item>(); updateItemList(items); this.layoutInflator = layoutInflator; this.activity = activity; } public void updateItemList(List<Item> updatedItems) { if (updatedItems != null && updatedItems.size() > 0) { // FIND ALL THE DUPLICATES AND UPDATE IF NESSICARY List<Item> nonDuplicateItems = new ArrayList<Item>(); for (Item newItem : updatedItems) { boolean isDuplicate = false; for (Item oldItem : items) { if (oldItem.getId().equals(newItem.getId())) { // IF IT IS A DUPLICATE, UPDATE THE EXISTING ONE oldItem.update(newItem); isDuplicate = true; break; } } // IF IT IS NOT A DUPLICATE, ADD IT TO THE NON-DUPLICATE LIST if (isDuplicate == false) { nonDuplicateItems.add(newItem); } } // MERGE nonDuplicateItems.addAll(items); // SORT Collections.sort(nonDuplicateItems, new Item.ItemOrderComparator()); // CLEAR this.items.clear(); // ADD BACK IN this.items.addAll(nonDuplicateItems); // REFRESH notifyDataSetChanged(); } } public void removeItem(Item item) { items.remove(item); notifyDataSetChanged(); } @Override public int getCount() { if (items == null) return 0; else return items.size(); } @Override public Item getItem(int position) { if (items == null || position > getCount()) return null; else return items.get(position); } @Override public long getItemId(int position) { return getItem(position).hashCode(); } @Override public int getItemViewType(int position) { Item item = getItem(position); if (item.getPhotoURL() != null && URLUtil.isValidUrl(item.getPhotoURL()) == true) { return VIEWTYPE_PIC; } return VIEWTYPE_NOPIC; } @Override public View getView(int position, View convertView, ViewGroup parent) { ItemHolder itemHolder; if (convertView == null) { if (getItemViewType(position) == VIEWTYPE_PIC) { convertView = layoutInflator.inflate(R.layout.item_pic, null); } else { convertView = layoutInflator.inflate(R.layout.item, null); } // THIS CONSTRUCTOR ALSO CALLS REFRESH ON THE HOLDER FOR US itemHolder = new ItemHolder(convertView, position); convertView.setTag(itemHolder); } else { itemHolder = ((ItemHolder) convertView.getTag()); itemHolder.refreshHolder(position); } return convertView; } @Override public int getViewTypeCount() { return 2; } @Override public boolean hasStableIds() { return false; } @Override public boolean isEmpty() { return (getCount() < 1); } @Override public boolean areAllItemsEnabled() { return true; } @Override public boolean isEnabled(int position) { return true; } } ``` Ok I have now tried this ``` @Override public boolean hasStableIds() { return true; } @Override public long getItemId(int position) { return getItem(position).hashCode(); } ``` and this ``` @Override public boolean hasStableIds() { return false; } @Override public long getItemId(int position) { return getItem(position).hashCode(); } ``` where my hashcode is a reflection builder from apache used like so (Should work cause the hash changes based on values) ``` @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } ``` and it didn't work. From what I can tell stableIds is doing nothing. EDIT: none of these work either, in any combination of stable Ids. Once again, and the same as always, you have to scroll the view offscreen and then back on in order for it to be updated. ``` listview.refreshDrawableState(); listview.requestLayout(); listview.invalidateViews(); ```