Android ListView tidak menyegarkan setelah notifyDataSetChanged

116

Kode ListFragment saya

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 saya

<?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>

Tapi ini tidak menyegarkan ListView. Bahkan setelah memulai ulang aplikasi, item yang diperbarui tidak ditampilkan. Saya ItemAdaptermeluasBaseAdapter

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

Bisakah seseorang membantu?

Pembuat kode
sumber
2
Sudahkah Anda menguji untuk melihat apakah operasi database bekerja dengan baik? Bagaimana tampilan adaptornya? Selain itu, jika Anda membuat objek on untuk adapterreferensi mengapa Anda mengujinya untuk null satu baris di bawah ini?
Luksprog
Kode tidak membuang pengecualian dan saya memeriksa menggunakan debug. Semua metode dijalankan tanpa kesalahan. Ya itu kesalahan konyol.
Coder

Jawaban:

229

Lihat onResumemetode Anda di ItemFragment:

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

apa yang baru saja Anda perbarui sebelum memanggil notifyDataSetChanged()bukanlah bidang adaptor private List<Item> items;tetapi bidang fragmen yang dideklarasikan secara identik. Adaptor masih menyimpan referensi ke daftar item yang Anda teruskan saat Anda membuat adaptor (misalnya di onCreate fragmen). Cara terpendek (dalam arti jumlah perubahan) tetapi bukan cara elegan untuk membuat kode Anda berperilaku seperti yang Anda harapkan adalah dengan mengganti baris:

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

dengan

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

Solusi yang lebih elegan:

1) hapus item private List<Item> items;dari ItemFragment- kita perlu menyimpan referensi ke item tersebut hanya di adaptor

2) ubah onCreate menjadi:

@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) tambahkan metode di ItemAdapter:

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

4) ubah onResume Anda menjadi:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Tomasz Gawel
sumber
Bukankah lebih bersih jika memindahkan seluruh dbHelper ke dalam Adaptor? Jadi Anda hanya akan menelepon adapter.swapItems();dan adaptor akan melakukan dbHelper.getItems()hal itu. Tapi terima kasih atas jawabannya :)
Ansgar
7
Mengapa Anda harus menghapus () dan menambahkan item lagi? Bukankah itu tujuan tepatnya notifyDataSetChanged()?
Phil Ryan
1
@tomsaz dapatkah Anda membantu saya dengan stackoverflow.com/questions/28148618/…
1
Terima kasih @tomsaz Gawel, swapItems Anda sangat membantu saya, saya tidak tahu mengapa adaptor saya.notifydatasetchanged tidak berfungsi, karena "daftar" yang saya lewati juga diperbarui, bahkan saya telah memeriksanya dengan mencetak log, Bisakah Anda jelaskan kepada saya ini konsep
Kimmi Dhingra
1
Jawaban ini benar. Masalahnya adalah bahwa ADAPTER'S Item ArrayList tidak diperbarui. Ini berarti Anda dapat memanggil notifydatasetchanged sampai wajah Anda menjadi biru tanpa efek apa pun. Adapter memperbarui kumpulan data Anda dengan kumpulan data yang sama sehingga TIDAK ada perubahan. Alternatif lain untuk solusi yang diposting dalam jawaban ini yang mungkin lebih bersih adalah: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li
23

Anda menugaskan item yang dimuat ulang ke item variabel global di onResume(), tetapi ini tidak akan terlihat di ItemAdapterkelas, karena memiliki variabel contoh sendiri yang disebut 'item'.

Untuk menyegarkan ListView, tambahkan refresh () di ItemAdapterkelas yang menerima data daftar yaitu item

class ItemAdapter
{
    .....

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

perbarui onResume()dengan kode berikut

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
sumber
1
Ini benar sekali. Konstruktor adaptor mengharapkan untuk diteruskan item, tetapi dia hanya memperbarui bidang kelas luar.
LuxuryMode
Hai Santhosh. Dapatkah Anda melihat masalah serupa: stackoverflow.com/questions/35850715/…
8

Di onResume () ubah baris ini

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

untuk

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

Masalahnya adalah Anda tidak pernah memberi tahu adaptor Anda tentang daftar item baru. Jika Anda tidak ingin meneruskan daftar baru ke adaptor Anda (sepertinya Anda tidak), maka gunakan saja items.addAllsetelah Anda clear(). Ini akan memastikan Anda memodifikasi daftar yang sama dengan referensi adaptor.

Justin Breitfeller
sumber
Ini membingungkan yang adapter.clear()tidak memaksa adaptor untuk menyadari bahwa tampilan harus di-refresh, tetapi adapter.add()atau adapter.addAll()tidak. Terima kasih atas jawabannya!
w3bshark
Perhatikan bahwa saya menggunakan items.addAll()dan bukan adapter.addAll (). Satu-satunya hal yang membiarkan adaptor bereaksi terhadap perubahan adalah notifyDataSetChanged. Alasan adaptor melihat perubahan sama sekali adalah itemsdaftar tersebut adalah daftar yang sama yang digunakan adaptor.
Justin Breitfeller
4

Jika adaptor sudah disetel, menyetelnya lagi tidak akan menyegarkan tampilan daftar. Sebagai gantinya, periksa dulu apakah listview memiliki adaptor dan kemudian panggil metode yang sesuai.

Saya pikir itu bukan ide yang sangat baik untuk membuat contoh baru dari adaptor saat mengatur tampilan daftar. Sebagai gantinya, buatlah sebuah objek.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Anda juga dapat mencoba menjalankan daftar refresh di UI Thread:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
sumber
Saya tidak yakin bagaimana menerapkan thread UI. Aktivitas utama saya memiliki 3 fragmen (tab) dan kode dalam pertanyaan terkait dengan salah satu fragmen yang berisi tampilan daftar. Alasan untuk meneruskan item ke ItemAdapteradalah saya ingin mewarnai baris dan tampilan daftar menampilkan beberapa item data. Saya telah memposting kode untuk adaptor.
Coder
Anda perlu memasukkan kode yang mengisi daftar Anda di kode contoh saya menggunakan "ini". bukannya "aktivitas"
AlexGo
Dalam beberapa kasus, itu tidak diperbarui ketika Anda menjalankan notifyDataSetChanged () di utas berbeda, Jadi solusi di atas tepat untuk beberapa kasus.
Ayman Al-Absi
4

Jika Anda ingin memperbarui tampilan daftar Anda tidak masalah jika Anda ingin melakukannya onResume(), onCreate()atau di beberapa fungsi lain, hal pertama yang harus Anda sadari adalah bahwa Anda tidak perlu membuat contoh baru dari adaptor, cukup isi array dengan data Anda lagi. Idenya mirip dengan ini:

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

Jadi tergantung pada jawaban itu, Anda harus menggunakan yang sama List<Item>di Fragment. Dalam inisialisasi adaptor pertama Anda, Anda mengisi daftar Anda dengan item dan mengatur adaptor ke listview Anda. Setelah itu di setiap perubahan pada item Anda, Anda harus menghapus nilai dari utama List<Item> itemsdan kemudian mengisinya lagi dengan item baru Anda dan memanggil notifySetDataChanged();.

Begitulah cara kerjanya :).

h4rd4r7c0r3
sumber
Terima kasih balasannya. Saya melakukan perubahan seperti yang Anda sebutkan. Saya telah memposting kode saya. Itu masih tidak berhasil. Sekarang bahkan tidak menampilkan tampilan daftar ketika item baru ditambahkan.
Coder
Saya telah mengubah kodenya. Hal yang aneh untuk diamati adalah bahwa item tersebut tidak diperbarui di DB
Coder
Utas ini untuk database stackoverflow.com/questions/14555332/…
Coder
3

Jawaban dari AlexGo berhasil untuk saya:

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

Pembaruan Daftar berfungsi untuk saya sebelumnya ketika pembaruan dipicu dari acara GUI, sehingga berada di utas UI.

Namun, ketika saya memperbarui daftar dari acara / utas lain - yaitu panggilan dari luar aplikasi, pembaruan tidak akan berada di utas UI dan mengabaikan panggilan ke getListView. Memanggil pembaruan dengan runOnUiThread seperti di atas melakukan trik bagi saya. Terima kasih!!

pengguna2996950
sumber
3

Coba ini

@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();
}
Gautami
sumber
3
adpter.notifyDataSetInvalidated();

Coba ini dalam onPause()metode kelas Aktivitas.

Som
sumber
1
adapter.setNotifyDataChanged()

harus melakukan triknya.

Hitman
sumber
3
di mana harus meletakkan pertanyaannya di sini ??
swiftBoy
1

Jika daftar Anda terdapat dalam Adaptor itu sendiri, memanggil fungsi yang memperbarui daftar juga harus memanggil notifyDataSetChanged().

Menjalankan fungsi ini dari UI Thread melakukan trik untuk saya:

The refresh()fungsi dalam Adapter

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

Kemudian secara bergiliran jalankan fungsi ini dari UI Thread

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Dévan Coetzee
sumber
Ini memang membuat perbedaan bagi saya karena pembaruan datang melalui jaringan melalui utas yang berbeda.
Chuck
0

Coba seperti ini:

this.notifyDataSetChanged();

dari pada:

adapter.notifyDataSetChanged();

Anda harus notifyDataSetChanged()ke ListViewtidak ke kelas adaptor.

Jachu
sumber
tentu saja tidak, satu-satunya kesempatan jika aktivitas diperpanjang oleh listview
cmario