Question

My ListView is using an extension of BaseAdapter, I can not get it to refresh properly. When I refresh, it appears that the old data draws on top of the new data, until a scroll event happens. The old rows draw on top of the new rows, but the old rows disappear when I start scrolling.

I have tried calling invalidateViews(), notifyDataSetChanged(), and notifyDataSetInvalidated(). My code looks something like:

private void updateData()
{
   List<DataItems> newList = getNewList();

   MyAdapter adapter = new MyAdapter(getContext());
   //my adapter holds an internal list of DataItems
   adapter.setList(newList);
   mList.setAdapter(adapter);
   adapter.notifyDataSetChanged();
   mList.invalidateViews();
}
Was it helpful?

Solution

To those still having problems, I solved it this way:

List<Item> newItems = databaseHandler.getItems();
ListArrayAdapter.clear();
ListArrayAdapter.addAll(newItems);
ListArrayAdapter.notifyDataSetChanged();
databaseHandler.close();

I first cleared the data from the adapter, then added the new collection of items, and only then set notifyDataSetChanged(); This was not clear for me at first, so I wanted to point this out. Take note that without calling notifyDataSetChanged() the view won't be updated.

OTHER TIPS

In my understanding, if you want to refresh ListView immediately when data has changed, you should call notifyDataSetChanged() in RunOnUiThread().

private void updateData() {
    List<Data> newData = getYourNewData();
    mAdapter.setList(yourNewList);

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
                mAdapter.notifyDataSetChanged();
        }
    });
}

You don't have to create a new adapter to update your ListView's contents. Simply store your Adapter in a field and update your list with the following code:

mAdapter.setList(yourNewList);
mAdapter.notifyDataSetChanged();

To clarify that, your Activity should look like that:

private YourAdapter mAdapter;

protected void onCreate(...) {

    ...

    mAdapter = new YourAdapter(this);
    setListAdapter(mAdapter);

    updateData();
}

private void updateData() {
    List<Data> newData = getYourNewData();
    mAdapter.setList(yourNewList);
    mAdapter.notifyDataSetChanged();
}

I too have tried invalidate(), invalidateViews(), notifyDataSetChanged(). They all might work in some particular contexts but it did not do the job in my case.

In my case, I had to add some new rows to the list and it just does not work. Creating a new adapter solved the issue.

While debugging, I realized that the data was there but just not rendered. If invalidate() or invalidateViews() does not render (which it is supposed to), I don't know what would.

Creating a new Adapter object to refresh modified data does not seem to be a bad idea. It definitely works. The only downside could be the time consumed in allocating new memory for your adapter but that should be OK assuming the Android system is smart enough and takes care of that to keep it efficient.

If you take this out of the equation, the flow is almost same as to calling notifyDataSetChanged. In the sense, the same set of adapter functions are called in either case.

So we are not losing much but gaining a lot.

I think refilling the same adapter with different data would be more or most better technique. Put this method in your Adapter class with right argument (the data list you want to display as names in my case) Call this where u update the data of list with updated list (names in my case)

public void refill(ArrayList<BeanDatabase> names) {
    list.clear();
    list.addAll(names);
    list.notifyDataSetChanged();
}

If you change the adapter or set the adapter again and again on when the list updates, then force close error would surely cause problems at some point. (Error:List data been updated but adapter doesn't notify the List View)

I'm doing the same thing using invalidateViews() and that works for me. If you want it to invalidate immediately you could try calling postInvalidate after calling invalidateViews.

Update ListView's contents by below code:

private ListView listViewBuddy;
private BuddyAdapter mBuddyAdapter;
private ArrayList<BuddyModel> buddyList = new ArrayList<BuddyModel>();

onCreate():

listViewBuddy = (ListView)findViewById(R.id.listViewBuddy);
mBuddyAdapter = new BuddyAdapter();
listViewBuddy.setAdapter(mBuddyAdapter);

onDataGet (After webservice call or from local database or otherelse):

mBuddyAdapter.setData(buddyList);
mBuddyAdapter.notifyDataSetChanged();

BaseAdapter:

private class BuddyAdapter extends BaseAdapter { 

    private ArrayList<BuddyModel> mArrayList = new ArrayList<BuddyModel>();
    private LayoutInflater mLayoutInflater= (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    private ViewHolder holder;

    public void setData(ArrayList<BuddyModel> list){
        mArrayList = list;
    }

    @Override
    public int getCount() {
        return mArrayList.size();
    }

    @Override
    public BuddyModel getItem(int position) {
        return mArrayList.get(position);
    }

    @Override
    public long getItemId(int pos) {
        return pos;
    }

    private class ViewHolder {
        private TextView txtBuddyName, txtBuddyBadge;

    }
    @SuppressLint("InflateParams")
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = mLayoutInflater.inflate(R.layout.row_buddy, null);
            // bind views
            holder.txtBuddyName = (TextView) convertView.findViewById(R.id.txtBuddyName);
            holder.txtBuddyBadge = (TextView) convertView.findViewById(R.id.txtBuddyBadge);

            // set tag
            convertView.setTag(holder);
        } else {
            // get tag
            holder = (ViewHolder) convertView.getTag();
        }

        holder.txtBuddyName.setText(mArrayList.get(position).getFriendId());

        int badge = mArrayList.get(position).getCount();
        if(badge!=0){
            holder.txtBuddyBadge.setVisibility(View.VISIBLE);
            holder.txtBuddyBadge.setText(""+badge);
        }else{
            holder.txtBuddyBadge.setVisibility(View.GONE);
        }   

        return convertView;
    }
}

Whenever you want to Update Listview just call below two lines code:

 mBuddyAdapter.setData(Your_Updated_ArrayList);
 mBuddyAdapter.notifyDataSetChanged();

Done

Only this works for me everytime, note that I don't know if it causes any other complications or performance issues:

private void updateListView(){
        listview.setAdapter(adapter);
    }

Another easy way:

//In your ListViewActivity:
public void refreshListView() {
    listAdapter = new ListAdapter(this);
    setListAdapter(listAdapter);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top