38

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();
}
Amirhossein Mehrvarzi
  • 11,037
  • 6
  • 38
  • 65
ab11
  • 18,490
  • 37
  • 104
  • 194

9 Answers9

60

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.

Arnon Zilca
  • 3,704
  • 4
  • 29
  • 40
LoneDuck
  • 1,722
  • 1
  • 19
  • 26
13

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();
        }
    });
}
Fry
  • 6,036
  • 8
  • 48
  • 89
khcpietro
  • 1,150
  • 2
  • 16
  • 36
  • 1
    I've been combing StackOverflow for two hours, and found only hundreds of unhelpful "call notifyDataSetChanged" advice and the like. Your answer, though, actually works. Thanks! – Nevermind Nov 01 '13 at 12:03
  • It is saying that mAdapter should be final? –  Mar 18 '14 at 16:00
  • @FarazAhmad If mAdapter is local variable it should be final, but if mAdapter is instance(of Activity) variable, it doesn't have to be :) – khcpietro Aug 28 '14 at 14:02
  • Tried a bunch of other solution on SO! This one worked perfectly! Thanks a ton, @Aigori! – Rahil Arora Nov 11 '14 at 01:09
12

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();
}
Gubbel
  • 2,246
  • 1
  • 23
  • 35
  • 1
    Hmm this code should work, could you post the part where you set your Adapter and the setList() method? – Gubbel Feb 05 '11 at 13:44
  • So, if my ListActivity is set to full screen (style/Theme.NoBackground) it has the issues that I described. But if I set it to Dialog theme, it refreshes properly. – ab11 Feb 05 '11 at 22:02
  • well, the adapter is set as shown in my original post, with the setAdapter(...) method. and my setList() method doesn't do anything interesting, it just sets a member variable of the adapter, which is used to determine the view for each row. – ab11 Feb 06 '11 at 20:49
  • 1
    @ab11 As I already said, you don't set the Adapter in your `updateData()` method. Instead set it in `onCreate()` and and call those methods described in my post on that Adapter. – Gubbel Feb 06 '11 at 22:10
  • thanks. sorry, I did try this as well. (which is what meant by "... i tried this without success.."), but I realize that comment was pretty vague. – ab11 Feb 06 '11 at 23:17
  • Out of curiosity, what's the major downfall of setting the `setAdapter()` method every `onResume()`. Memory, performance, etc.? – Joshua Pinter Jan 23 '14 at 20:33
4

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)

CyberEd
  • 840
  • 9
  • 17
himb2001
  • 1,179
  • 1
  • 9
  • 7
3

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.

Deepak G M
  • 1,593
  • 16
  • 12
  • 1
    I found setting a background color for the listview eliminated a lot of my refresh issues. – ab11 Sep 08 '11 at 11:56
2

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

Hiren Patel
  • 48,538
  • 20
  • 161
  • 144
2

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.

Carl Manaster
  • 38,312
  • 15
  • 96
  • 147
ingo
  • 5,099
  • 1
  • 22
  • 18
1

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);
    }
savante
  • 785
  • 1
  • 6
  • 19
1

Another easy way:

//In your ListViewActivity:
public void refreshListView() {
    listAdapter = new ListAdapter(this);
    setListAdapter(listAdapter);
}
dnst
  • 11
  • 1