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();
There is a similar issue here with a solution that may work:
ListView not refreshing already-visible items
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With