I could not find anything specific related to my exact problem, please read to find out what it is.
I took great care to make sure that everywhere in my code, I am set up directly to just call notifyDataSetChanged on the adapter, I initialize the itemList once and pass it to the adapter and never reinstall it again ever.
It works like a charm, and the list view will be updated, but only for new items.
For existing items, the ListView will not update correctly.
For example, if I have a list 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 list will always be updated to show new items, but it will not update the views of existing items.
Here is a real kicker that tells me that this is a problem with the views: if I call adapter.getItem(position); in an updated previously existing element, the returned element will show the updated changes (the value text1 and text2 will contain their new values), although this is not reflected in the list!
If I call listView.invalidateViews(); , then updates will be shown in the list view, but I have two problems with this, sometimes it flickers, and sometimes just sometimes, if I call it, and it works before notifyDataSetChanged can finish getting to the list, I get a message "View list without notification of changes" error!
Does anyone know 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 I need to repeat the creation of all my elements before adding to the list using the copy constructor. Perhaps when notifying the adapter, the adapter will assume that there are no changes if item is still the same link and therefore will not redraw this view? or maybe the adapter only gets new views for new items when notified?
To add another detail, if I scroll down to refresh the view, turn off the screen and then go back to it, it will display the correct information, since the listview updates / changes this view.
It seems to me that I need listview to update all current views, so invalidateViews(); maybe what i have to do.
Does anyone know more about this?
EDIT: As requested, there is an adapter here that will have this problem.
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 tried this
@Override public boolean hasStableIds() { return true; } @Override public long getItemId(int position) { return getItem(position).hashCode(); }
and this one
@Override public boolean hasStableIds() { return false; } @Override public long getItemId(int position) { return getItem(position).hashCode(); }
where my hash code is a builder of reflections from apache used like this (if the operation causes hash changes based on values)
@Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); }
and it didn’t work. From what I can say, stableIds does nothing.
EDIT:
none of them work in any combination of stable identifiers. Once again and in the same way as always, you need to scroll the view on the screen, and then return to get it updated.
listview.refreshDrawableState(); listview.requestLayout(); listview.invalidateViews();