Android ListView не обновляется после notifyDataSetChanged

Мой код ListFragment

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);
        }
    }
}

Мой 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>

Но это не обновляет ListView. Даже после перезапуска приложения обновленные элементы не отображаются. Мой ItemAdapter расширяет 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;
    }
}

Может кто-нибудь помочь, пожалуйста?


person Coder    schedule 24.01.2013    source источник
comment
Вы проверяли, правильно ли работает работа с базой данных? Как выглядит адаптер? Кроме того, если вы создаете объект on для ссылки adapter, почему вы проверяете его на null на одну строку ниже?   -  person user    schedule 24.01.2013
comment
Код не генерирует исключение, и я проверил его с помощью отладки. Все методы выполнены без ошибок. Да, это глупая ошибка.   -  person Coder    schedule 24.01.2013


Ответы (11)


Посмотрите на свой onResume метод в ItemFragment:

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

то, что вы только что обновили перед вызовом notifyDataSetChanged(), является не полем адаптера private List<Item> items;, а идентично объявленным полем фрагмента. Адаптер по-прежнему хранит ссылку на список элементов, которые вы передали при создании адаптера (например, во фрагменте onCreate). Самый короткий (в смысле количества изменений), но не элегантный способ заставить ваш код вести себя так, как вы ожидаете, - это просто заменить строку:

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

с участием

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

Более элегантное решение:

1) удалить элементы private List<Item> items; из ItemFragment - нам нужно сохранить ссылку на них только в адаптере

2) измените onCreate на:

@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) добавить метод в ItemAdapter:

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

4) измените свой onResume на:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
person Tomasz Gawel    schedule 03.02.2013
comment
Разве не было бы чище переместить всю вещь dbHelper в адаптер? Таким образом, вы вызовете только adapter.swapItems();, а адаптер сделает все dbHelper.getItems(). Но все равно спасибо за ответ :) - person Ansgar; 17.02.2014
comment
Почему вам нужно очистить () и снова добавить элементы? Разве это не цель notifyDataSetChanged()? - person Phil Ryan; 26.08.2014
comment
@tomsaz, вы можете мне помочь с этим stackoverflow.com/questions/28148618/ - person ; 27.01.2015
comment
Спасибо @tomsaz Gawel, ваши swapItems действительно мне очень помогают, я не знаю, почему мой адаптер .notifydatasetchanged не работает, поскольку список, который я передаю, также обновляется, даже я проверил его, распечатав журнал, не могли бы вы объяснить мне эту концепцию - person Kimmi Dhingra; 31.07.2015
comment
Томаш, можете ли вы помочь с подобным вопросом: stackoverflow.com/questions/35850715/ Я пробовал это решение, но безуспешно. - person ; 08.03.2016
comment
Это правильный ответ. Проблема в том, что список массивов элементов АДАПТЕРА не обновлялся. Это означает, что вы можете вызывать notifydatasetchanged до тех пор, пока ваше лицо не станет синим без какого-либо эффекта. Адаптер обновляет ваш набор данных тем же набором данных, поэтому НЕТ изменений. Другая альтернатива решению, опубликованному в этом ответе, которое могло бы быть более чистым: adapter.items = items; адаптер.notifyDataSetChanged (); - person Ray Li; 18.04.2017

Вы назначаете повторно загруженные элементы элементам глобальной переменной в onResume(), но это не отразится в классе ItemAdapter, потому что у него есть собственная переменная экземпляра, называемая 'items'.

Для обновления ListView добавьте в класс ItemAdapter функцию refresh (), которая принимает данные списка, т.е. элементы

class ItemAdapter
{
    .....

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

обновите onResume() следующим кодом

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
person Santhosh    schedule 30.01.2013
comment
Совершенно верно. Конструктор адаптера ожидает передачи элементов, но он обновляет только поле внешнего класса. - person LuxuryMode; 03.02.2013
comment
Привет, Сантош. Можете ли вы взглянуть на аналогичную проблему: stackoverflow.com/questions/35850715/ - person ; 08.03.2016

В onResume () измените эту строку

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

to

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

Проблема в том, что вы никогда не сообщаете своему адаптеру список новых элементов. Если вы не хотите передавать новый список вашему адаптеру (а кажется, что вы этого не делаете), просто используйте items.addAll после вашего clear(). Это гарантирует, что вы изменяете тот же список, на который ссылается адаптер.

person Justin Breitfeller    schedule 01.02.2013
comment
Это сбивает с толку, что adapter.clear() не заставляет адаптер осознавать, что представление должно обновляться, но adapter.add() или adapter.addAll() это делает. Спасибо за ответ! - person w3bshark; 01.08.2015
comment
Обратите внимание, что я использовал items.addAll(), а не adapter.addAll (). Единственное, что позволяет адаптеру реагировать на изменения, - это notifyDataSetChanged. Причина, по которой адаптер вообще видит изменения, заключается в том, что items список - это тот же список, который использует адаптер. - person Justin Breitfeller; 13.08.2015

Если адаптер уже настроен, повторная установка не приведет к обновлению списка. Вместо этого сначала проверьте, есть ли у listview адаптер, а затем вызовите соответствующий метод.

Я думаю, что создавать новый экземпляр адаптера при настройке представления списка - не лучшая идея. Вместо этого создайте объект.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Также вы можете попробовать запустить список обновлений в потоке пользовательского интерфейса:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
person AlexGo    schedule 24.01.2013
comment
Я не уверен, как реализовать поток пользовательского интерфейса. Моя основная деятельность состоит из 3 фрагментов (вкладок), а код в вопросе связан с одним из фрагментов, содержащих представление списка. Причина передачи элементов в ItemAdapter заключается в том, что я хочу раскрасить строки, а в представлении списка отображается несколько элементов данных. Я разместил код адаптера. - person Coder; 27.01.2013
comment
Вам нужно поместить свой код, который заполняет ваш список, в моем примере кода, используя this. вместо активности - person AlexGo; 28.01.2013
comment
В некоторых случаях он не обновляется, когда вы запускаете notifyDataSetChanged () в другом потоке, поэтому приведенное выше решение подходит для некоторых случаев. - person Ayman Al-Absi; 28.04.2014

Если вы хотите обновить свой список, не имеет значения, хотите ли вы сделать это в onResume(), onCreate() или в какой-либо другой функции, первое, что вам нужно понять, это то, что вам не нужно создавать новый экземпляр адаптера, просто снова заполните массивы своими данными. Идея примерно такая:

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();
}

Итак, в зависимости от этого ответа вы должны использовать тот же List<Item> в своем Fragment. При первой инициализации адаптера вы заполняете список элементами и настраиваете адаптер для просмотра списка. После этого при каждом изменении в ваших элементах вы должны очищать значения из основного List<Item> items, а затем снова заполнять его своими новыми элементами и вызывать notifySetDataChanged();.

Вот как это работает :).

person hardartcore    schedule 27.01.2013
comment
Спасибо за ответ. Я внес изменения, как вы упомянули. Я разместил свой код. Это все еще не работает. Теперь он даже не отображает список при добавлении новых элементов. - person Coder; 28.01.2013
comment
Я поменял код. Странно наблюдать, что элемент не обновляется в БД. - person Coder; 28.01.2013
comment
Этот поток предназначен для базы данных stackoverflow.com/ questions / 14555332 / - person Coder; 28.01.2013

Ответ AlexGo помог мне:

getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
         messages.add(m);
         adapter.notifyDataSetChanged();
         getListView().setSelection(messages.size()-1);
        }
});

Обновление списка работало для меня раньше, когда обновление запускалось из события графического интерфейса пользователя, таким образом, находясь в потоке пользовательского интерфейса.

Однако, когда я обновляю список из другого события / потока, то есть вызова извне приложения, обновление не будет в потоке пользовательского интерфейса, и он проигнорирует вызов getListView. Вызов обновления с помощью runOnUiThread, как указано выше, помог мне. Спасибо!!

person user2996950    schedule 20.11.2013

Попробуй это

@Override
public void onResume() {
super.onResume();
items.clear();
items = dbHelper.getItems(); //reload the items from database
adapter = new ItemAdapter(getActivity(), items);//reload the items from database
adapter.notifyDataSetChanged();
}
person Gautami    schedule 22.04.2014

adpter.notifyDataSetInvalidated();

Попробуйте это в onPause() методе класса Activity.

person Som    schedule 01.02.2013

adapter.setNotifyDataChanged()

должен сделать свое дело.

person Hitman    schedule 20.11.2013
comment
куда ставить вот вопрос ?? - person swiftBoy; 18.02.2014

Если ваш список содержится в самом адаптере, вызов функции, обновляющей список, также должен вызывать notifyDataSetChanged().

Запуск этой функции из потока пользовательского интерфейса помог мне:

Функция refresh() внутри адаптера

public void refresh(){
    //manipulate list
    notifyDataSetChanged();
}

Затем, в свою очередь, запустите эту функцию из потока пользовательского интерфейса

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
person Dévan Coetzee    schedule 22.06.2017
comment
Это действительно имело для меня значение, поскольку обновление пришло по сети через другой поток. - person Chuck; 16.12.2019

Попробуйте вот так:

this.notifyDataSetChanged();

вместо:

adapter.notifyDataSetChanged();

Вы должны notifyDataSetChanged() ListView, а не класс адаптера.

person Jachu    schedule 31.01.2013
comment
конечно не будет, единственный шанс, если активность будет расширена списком - person cmario; 25.02.2016