Android ListView not refreshing after notifyDataSetChanged

197,907

Solution 1

Look at your onResume method in ItemFragment:

@Override
public void onResume() {
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); // reload the items from database
    adapter.notifyDataSetChanged();
}

what you just have updated before calling notifyDataSetChanged() is not the adapter's field private List<Item> items; but the identically declared field of the fragment. The adapter still stores a reference to list of items you passed when you created the adapter (e.g. in fragment's onCreate). The shortest (in sense of number of changes) but not elegant way to make your code behave as you expect is simply to replace the line:

    items = dbHelper.getItems(); // reload the items from database

with

    items.addAll(dbHelper.getItems()); // reload the items from database

A more elegant solution:

1) remove items private List<Item> items; from ItemFragment - we need to keep reference to them only in adapter

2) change onCreate to :

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setHasOptionsMenu(true);
    getActivity().setTitle(TITLE);
    dbHelper = new DatabaseHandler(getActivity());
    adapter = new ItemAdapter(getActivity(), dbHelper.getItems());
    setListAdapter(adapter);
}

3) add method in ItemAdapter:

public void swapItems(List<Item> items) {
    this.items = items;
    notifyDataSetChanged();
}

4) change your onResume to:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}

Solution 2

You are assigning reloaded items to global variable items in onResume(), but this will not reflect in ItemAdapter class, because it has its own instance variable called 'items'.

For refreshing ListView, add a refresh() in ItemAdapter class which accepts list data i.e items

class ItemAdapter
{
    .....

    public void refresh(List<Item> items)
    {
        this.items = items;
        notifyDataSetChanged();
    } 
}

update onResume() with following code

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}

Solution 3

In onResume() change this line

items = dbHelper.getItems(); //reload the items from database

to

items.addAll(dbHelper.getItems()); //reload the items from database

The problem is that you're never telling your adapter about the new items list. If you don't want to pass a new list to your adapter (as it seems you don't), then just use items.addAll after your clear(). This will ensure you are modifying the same list that the adapter has a reference to.

Solution 4

If the adapter is already set, setting it again will not refresh the listview. Instead first check if the listview has a adapter and then call the appropriate method.

I think its not a very good idea to create a new instance of the adapter while setting the list view. Instead, create an object.

BuildingAdapter adapter = new BuildingAdapter(context);

    if(getListView().getAdapter() == null){ //Adapter not set yet.
     setListAdapter(adapter);
    }
    else{ //Already has an adapter
    adapter.notifyDataSetChanged();
    }

Also you might try to run the refresh list on UI Thread:

activity.runOnUiThread(new Runnable() {         
        public void run() {
              //do your modifications here

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});

Solution 5

If you want to update your listview doesn't matter if you want to do that on onResume(), onCreate() or in some other function, first thing that you have to realize is that you won't need to create a new instance of the adapter, just populate the arrays with your data again. The idea is something similar to this :

private ArrayList<String> titles;
private MyListAdapter adapter;
private ListView myListView;

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    myListView = (ListView) findViewById(R.id.my_list);

    titles = new ArrayList<String>()

    for(int i =0; i<20;i++){
        titles.add("Title "+i);
    }

    adapter = new MyListAdapter(this, titles);
    myListView.setAdapter(adapter);
}


@Override
public void onResume(){
    super.onResume();
    // first clear the items and populate the new items
    titles.clear();
    for(int i =0; i<20;i++){
        titles.add("New Title "+i);
    }
    adapter.notifySetDataChanged();
}

So depending on that answer you should use the same List<Item> in your Fragment. In your first adapter initialization you fill your list with the items and set adapter to your listview. After that in every change in your items you have to clear the values from the main List<Item> items and than populate it again with your new items and call notifySetDataChanged();.

That's how it works : ).

Share:
197,907
Coder
Author by

Coder

Updated on June 23, 2020

Comments

  • Coder
    Coder almost 4 years

    My ListFragment code

    public class ItemFragment extends ListFragment {
    
        private DatabaseHandler dbHelper;
        private static final String TITLE = "Items";
        private static final String LOG_TAG = "debugger";
        private ItemAdapter adapter;
        private List<Item> items;
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.item_fragment_list, container, false);        
            return view;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.setHasOptionsMenu(true);
            super.onCreate(savedInstanceState);
            getActivity().setTitle(TITLE);
            dbHelper = new DatabaseHandler(getActivity());
            items = dbHelper.getItems(); 
            adapter = new ItemAdapter(getActivity().getApplicationContext(), items);
            this.setListAdapter(adapter);
    
        }
    
    
    
        @Override
        public void onResume() {
            super.onResume();
            items.clear();
            items = dbHelper.getItems(); //reload the items from database
            adapter.notifyDataSetChanged();
        }
    
        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            super.onListItemClick(l, v, position, id);
            if(dbHelper != null) { //item is edited
                Item item = (Item) this.getListAdapter().getItem(position);
                Intent intent = new Intent(getActivity(), AddItemActivity.class);
                intent.putExtra(IntentConstants.ITEM, item);
                startActivity(intent);
            }
        }
    }
    

    My ListView

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <ListView
            android:id="@android:id/list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    But this does not refresh the ListView. Even after restarting app the updated items are not shown. My ItemAdapter extends BaseAdapter

    public class ItemAdapter extends BaseAdapter{
    
        private LayoutInflater inflater;
        private List<Item> items;
        private Context context;
    
        public ProjectListItemAdapter(Context context, List<Item> items) {
            super();
            inflater = LayoutInflater.from(context);
            this.context = context;
            this.items = items;
    
        }
    
        @Override
        public int getCount() {
            return items.size();
        }
    
        @Override
        public Object getItem(int position) {
            return items.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ItemViewHolder holder = null;
            if(convertView == null) {
                holder = new ItemViewHolder();
                convertView = inflater.inflate(R.layout.list_item, parent,false);
                holder.itemName = (TextView) convertView.findViewById(R.id.topText);
                holder.itemLocation = (TextView) convertView.findViewById(R.id.bottomText);
                convertView.setTag(holder);
            } else {
                holder = (ItemViewHolder) convertView.getTag();
            }
            holder.itemName.setText("Name: " + items.get(position).getName());
            holder.itemLocation.setText("Location: " + items.get(position).getLocation());
            if(position % 2 == 0) {                                                                                 
                convertView.setBackgroundColor(context.getResources().getColor(R.color.evenRowColor));
            } else {    
                convertView.setBackgroundColor(context.getResources().getColor(R.color.oddRowColor));
            }
            return convertView;
        }
    
        private static class ItemViewHolder {
            TextView itemName;
            TextView itemLocation;
        }
    }
    

    Can someone help please?

  • Coder
    Coder over 11 years
    I am not sure how to implement UI thread. My main activity has 3 fragments (tabs) and the code in the question is related to one of the fragment that contains list view. The reason for passing items to ItemAdapter is I want to color the rows and the list view displays multiple data items. I have posted the code for adapter.
  • Coder
    Coder over 11 years
    Thanks for the reply. I did the changes as you mentioned. I have posted my code. It still doesnt work. Now it doesnt even show list view when new items are added.
  • Coder
    Coder over 11 years
    I have changed the code. The strange thing to observe is that item is not getting updated in DB
  • Coder
    Coder over 11 years
    This thread is for the database stackoverflow.com/questions/14555332/…
  • AlexGo
    AlexGo over 11 years
    You need to put your code that populates your list in my example code using "this." instead of "activity"
  • LuxuryMode
    LuxuryMode about 11 years
    This is exactly right. The adapter's constructor expects to be passed items, but he only ever updates the outer class's field.
  • Ansgar
    Ansgar about 10 years
    Wouldn't it be cleaner to move the whole dbHelper thing into the Adapter? So you would only call adapter.swapItems(); and the adapter would do the dbHelper.getItems() stuff. But anyway thanks for the answer :)
  • swiftBoy
    swiftBoy about 10 years
    where to put is the question here??
  • Ayman Al-Absi
    Ayman Al-Absi about 10 years
    In some cases it is not updated when you run the notifyDataSetChanged() in different thread, So the above solution is right for some cases.
  • Phil Ryan
    Phil Ryan over 9 years
    Why should you have to clear() and add the items again? Isn't that exactly the purpose of notifyDataSetChanged()?
  • Admin
    Admin over 9 years
    @tomsaz can you help me with this stackoverflow.com/questions/28148618/…
  • Kimmi Dhingra
    Kimmi Dhingra almost 9 years
    Thanks @tomsaz Gawel, your swapItems really help me alot, I dont know why my adapter.notifydatasetchanged not works, as the "list" i am passing is also updated, even I have checked it by printing log, Can you Please explain me this concept
  • w3bshark
    w3bshark almost 9 years
    It's confusing that adapter.clear() doesn't force the adapter to realize that the view should refresh, but adapter.add() or adapter.addAll() does. Thanks for the answer though!
  • Justin Breitfeller
    Justin Breitfeller over 8 years
    Note that I was using items.addAll() and not adapter.addAll(). The only thing that is letting the adapter react to changes is the notifyDataSetChanged. The reason the adapter sees changes at all is the items list is the same list that the adapter is using.
  • cmario
    cmario about 8 years
    of course it won't, the only chance if the activity is extended by a listview
  • Admin
    Admin about 8 years
    Tomasz can you help with a similar question: stackoverflow.com/questions/35850715/… I have tried this solution but with no succses.
  • Admin
    Admin about 8 years
    Hi Santhosh. Can you take a look at a similar problem: stackoverflow.com/questions/35850715/…
  • Ray Li
    Ray Li about 7 years
    This answer is correct. The problem is that the ADAPTER'S Item ArrayList was not being updated. This means you can call notifydatasetchanged until your face is blue without any effect. The Adapter is updating your dataset with the same dataset so there are NO changes. Another alternative to the solution posted in this answer that might be cleaner is: adapter.items = items; adapter.notifyDataSetChanged();
  • Chuck
    Chuck over 4 years
    This indeed made a difference for me as the update came over network via a different thread.