NotifydataSetChanged on Adapter will be updated with new elements, but will not update existing elements

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(); 
+7
java android
source share
8 answers

There is a similar problem with a solution that might work:

ListView does not update already visible items

+7
source share

With "unstable identifiers," everything should be fine if you call notifyDatasetChanged () , but your ListView doesn't seem to know that some existing items need to be updated.

Perhaps you can try to implement stable identifiers and use them incorrectly so that the identifier changes when elements are updated.

 @Override public long getItemId(int position) { return getItem(position).getId(); } @Override public boolean hasStableIds() { return true; } 

Another brute force approach would be to create a new adapter and install a new adapter for ListView.

+3
source share

I analyzed the code and the problem you were facing, and I came to the following conclusion:

The refreshHolder method refreshHolder called only when the convertView object has a value on the heap.

This means that if convertView not assigned to any value, the update will not occur.

The solution to this is to move itemHolder.refreshHolder(position) from the if-else condition block you inserted into.

+2
source share

adapter.notifyDataSetChanged () should execute the JOB, you need to make sure that if the List item that you work with outside the adapter is the same instance as the internal adapter. If notifyDataSetChanged () does not work for you, this is definitely the case.

Your adapter may also contain a “copy” of the list that you specified in the constructor, so your changes to the original list will not be displayed ... maybe you can enter a method for the adapter, for example: adapter.setItems (list items) to guarantee that the elements are really set

You do not need to call invalidateViews () on the ListView ... all you have to do is make sure the adapter has the correct list to display and run notifyDataSetChanged () .

+1
source share

instead:

 @Override public int getViewTypeCount() { return 2; } 

Try:

 @Override public int getViewTypeCount() { return getCount(); } 

and instead of putting

 ViewHolder viewHolder; 

in the getivew () method, try putting it at the top of the class before the class constructor

+1
source share

Instead of using convertView.set/getTag() , why not directly refresh the views

 refreshHolder(convertView, position); void refreshHolder(View v, int position) { ((TextView)v.findViewById(R.id.Text1)).setText(getItem(position).text1); ((TextView)v.findViewById(R.id.Text2)).setText(getItem(position).text2); } 

setTag / getTag will not be consistent in convertView when reusing views, and the same view will be reused when the view scrolls out of view and returns an incorrect ViewHolder value. Therefore, in most cases, the view is not updated.

0
source share

After much trial and error, this is what worked.

 public class AutoRefreshListView extends ListView { public AutoRefreshListView(Context context) { super(context); } public AutoRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); } public AutoRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } private DataSetObserver mDataSetObserver = new AdapterDataSetObserver(); private ListAdapter mAdapter; class AdapterDataSetObserver extends DataSetObserver { @Override public void onChanged() { super.onChanged(); Log.d("AutoRefreshListView", "onChanged"); refreshVisibleViews(); } @Override public void onInvalidated() { super.onInvalidated(); Log.d("AutoRefreshListView", "onInvalidated"); refreshVisibleViews(); } } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataSetObserver); } public void refreshVisibleViews() { Log.d("AutoRefreshListView", "refresh"); if (mAdapter != null) { for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i++) { final int dataPosition = i - getHeaderViewsCount(); final int childPosition = i - getFirstVisiblePosition(); if (dataPosition >= 0 && dataPosition < mAdapter.getCount() && getChildAt(childPosition) != null) { Log.d("AutoRefreshListView", "onInvalidated -> Refreshing view (data=" + dataPosition + ",child=" + childPosition + ")"); mAdapter.getView(dataPosition, getChildAt(childPosition), AutoRefreshListView.this); } } } } } 

decision from here

ListView does not update already visible items

found kupsef

0
source share

or you can do it like this: listadapter.clear(); listadapter.addAll(yourData); listadapter.clear(); listadapter.addAll(yourData);

0
source share

All Articles