notifyDataSetChange no funciona desde un adaptador personalizado

125

Cuando vuelvo a llenar mi ListView, llamo a un método específico desde mi Adapter.

Problema :

Cuando llamo updateReceiptsListdesde mi Adapter, los datos se actualizan, pero mi ListViewno refleja el cambio.

Pregunta :

¿Por qué mi no ListViewmuestra los nuevos datos cuando llamo notifyDataSetChanged?

Adaptador :

public class ReceiptListAdapter extends BaseAdapter {

    public List<Receipt> receiptlist;
    private Context context;
    private LayoutInflater inflater;
    private DateHelpers dateH;

    public ReceiptListAdapter(Activity activity, Context mcontext, List<Receipt> rl) {
        context = mcontext;
        receiptlist = rl;
        Collections.reverse(receiptlist);
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        dateH = new DateHelpers();
    }

    @Override
    public int getCount() {
        try {
            int size = receiptlist.size();
            return size;
        } catch(NullPointerException ex) {
            return 0;
        }
    }

    public void updateReceiptsList(List<Receipt> newlist) {
        receiptlist = newlist;
        this.notifyDataSetChanged();
    }

    @Override
    public Receipt getItem(int i) {
        return receiptlist.get(i);
    }

    @Override
    public long getItemId(int i) {
        return receiptlist.get(i).getReceiptId() ;
    }

    private String getPuntenString(Receipt r) {
        if(r.getPoints().equals("1")) {
            return "1 punt";
        }
        return r.getPoints()+" punten";
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi=convertView;

        final Receipt receipt = receiptlist.get(position);
        ReceiptViewHolder receiptviewholder;
        Typeface tf_hn = Typeface.createFromAsset(context.getAssets(), "helveticaneue.ttf");        
        Typeface tf_hn_bold = Typeface.createFromAsset(context.getAssets(), "helveticaneuebd.ttf");

        if (vi == null) { //convertview==null
            receiptviewholder = new ReceiptViewHolder();
            vi = inflater.inflate(R.layout.view_listitem_receipt, null);
            vi.setOnClickListener(null);
            vi.setOnLongClickListener(null);
            vi.setLongClickable(false);
            receiptviewholder.shop = (TextView) vi.findViewById(R.id.tv_listitemreceipt_shop);
            receiptviewholder.date = (TextView) vi.findViewById(R.id.tv_listitemreceipt_date);
            receiptviewholder.price = (TextView) vi.findViewById(R.id.tv_listitemreceipt_price);
            receiptviewholder.points = (TextView) vi.findViewById(R.id.tv_listitemreceipt_points);
            receiptviewholder.shop.setTypeface(tf_hn_bold);
            receiptviewholder.price.setTypeface(tf_hn_bold);
            vi.setTag(receiptviewholder);
        }else{//convertview is not null
            receiptviewholder = (ReceiptViewHolder)vi.getTag();
        }

        receiptviewholder.shop.setText(receipt.getShop());
        receiptviewholder.date.setText(dateH.timestampToDateString(Long.parseLong(receipt.getPurchaseDate())));
        receiptviewholder.price.setText("€ "+receipt.getPrice());
        receiptviewholder.points.setText(getPuntenString(receipt));

        vi.setClickable(false);
        return vi;
    }

    public static class ReceiptViewHolder {
        public TextView shop;
        public TextView date;
        public TextView price;
        public TextView points;
    }

    public Object getFilter() {
        // XXX Auto-generated method stub
        return null;
    }

}

--EDITAR:

Solución encontrada

Solo para tener un código funcional que hago ahora:

listview.setAdapter( new ReceiptListAdapter(activity,mcontext, -new dataset-);

Funciona, pero no cómo se supone que debe funcionar.

Jaspe
fuente
stackoverflow.com/a/4198569/2382964 , Hola Jasper, consulta este enlace ... esto te ayudará.
Tushar Pandey
pruebe otros métodos como notifyItemInserted, notifyItemRemoved, etc.
Reejesh PK

Respuestas:

334

Cambia tu método de

public void updateReceiptsList(List<Receipt> newlist) {
    receiptlist = newlist;
    this.notifyDataSetChanged();
}

A

public void updateReceiptsList(List<Receipt> newlist) {
    receiptlist.clear();
    receiptlist.addAll(newlist);
    this.notifyDataSetChanged();
}

Así que mantienes el mismo objeto que tu DataSet en tu Adaptador.

tolgap
fuente
considere tener un objeto principal como en ReceiptListObjectlugar de uno Listde objetos, ¿qué puede hacer para resolver este problema?
prom85
@ prom85 ¿los ArrayAdapters pueden incluso unirse a Objetos que no sean Listas o Arrays? Yo no sabía.
tolgap
se trata de BaseAdapteray este adaptador no sabe a qué datos está vinculado ... así que si tengo un objeto personalizado y uso funciones personalizadas de este objeto (como custObject.getCount()y custObject.getChildAt(int i)por ejemplo), y quiero intercambiar este objeto, notifyDataSetChangedno funciona ... de todos modos, creo que este problema nunca ocurre con unArrayAdapter
prom85
3
¿Puede explicar por qué el primer método no funciona y el segundo funciona con un Adaptador Base?
Tooroop
1
comentarios como Gracias o +1 no están permitidos, pero en algunas respuestas realmente me gusta dar las gracias :)
Muhammad Saqib
24

Tengo el mismo problema, y ​​me doy cuenta de eso. Cuando creamos un adaptador y lo configuramos en listview, listview apuntará a un objeto en algún lugar de la memoria que el adaptador contiene, los datos de este objeto se mostrarán en listview.

adapter = new CustomAdapter(data);
listview.setadapter(adapter);

si volvemos a crear un objeto para adaptador con otros datos y te notificamos adatasetchanged ():

adapter = new CustomAdapter(anotherdata);
adapter.notifyDataSetChanged();

esto no afectará a los datos en la vista de lista porque la lista apunta a un objeto diferente, este objeto no sabe nada sobre el nuevo objeto en el adaptador, y notifyDataSetChanged () no afecta a nada. Por lo tanto, debemos cambiar los datos en el objeto y evitar crear un nuevo objeto nuevamente para el adaptador

Nhan Tran
fuente
10
está recreando el objeto adaptador, no es eficiente
Alezis
2
@Alezis Creo que eso es exactamente lo que Nhan quiso decir.
Neeraj Sewani
17

Como ya he explicado las razones detrás de este problema y también cómo manejarlo en un hilo de respuesta diferente Aquí . Todavía estoy compartiendo el resumen de la solución aquí.

Una de las razones principales por las notifyDataSetChanged()que no funcionará es que

Su adaptador pierde referencia a su lista .

Al crear y agregar una nueva lista a Adapter. Siempre siga estas pautas:

  1. Inicialice arrayListmientras lo declara globalmente.
  2. Agregue la Lista al adaptador directamente sin verificar los valores nulos y vacíos. Establezca el adaptador en la lista directamente (no verifique ninguna condición). El adaptador le garantiza que siempre que realice cambios en los datos del arrayListmismo se encargará de ello, pero nunca perderá la referencia.
  3. Siempre modificar los datos en la propia arrayList (si sus datos son completamente nueva de lo que puede llamar adapter.clear()y arrayList.clear()antes de que realmente la adición de datos a la lista), pero no configurar el adaptador es decir, si los nuevos datos se rellena en el arrayListque sólo adapter.notifyDataSetChanged()

Espero que esto ayude.

Sazzad Hissain Khan
fuente
1
¡Gracias! La propina en el n. ° 2 ayudó.
Cheruby
4

Quizás intente actualizar su ListView:

receiptsListView.invalidate().

EDITAR: Otro pensamiento vino a mi mente. Solo para el registro, intente deshabilitar el caché de vista de lista:

<ListView
    ...
    android:scrollingCache="false"
    android:cacheColorHint="@android:color/transparent"
    ... />
Rafal Gałka
fuente
Extraño, mi última idea es reasignar el adaptador a la vista de lista después de cambiar los datos. Pero supongo que ya lo has intentado también.
Rafal Gałka
3

Tuve el mismo problema usando ListAdapter

Dejo que Android Studio implemente métodos para mí y esto es lo que obtuve:

public class CustomAdapter implements ListAdapter {
    ...
    @Override
    public void registerDataSetObserver(DataSetObserver observer) {

    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {

    }
    ...
}

El problema es que estos métodos no llaman a superimplementaciones, por notifyDataSetChangelo que nunca se llama.

Elimine estas anulaciones manualmente o agregue súper llamadas y debería funcionar nuevamente.

@Override
public void registerDataSetObserver(DataSetObserver observer) {
    super.registerDataSetObserver(observer);
}

@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
    super.unregisterDataSetObserver(observer);
}
Davor Zlotrg
fuente
2
class StudentAdapter extends BaseAdapter {
    ArrayList<LichHocDTO> studentList;

    private void capNhatDuLieu(ArrayList<LichHocDTO> list){
        this.studentList.clear();
        this.studentList.addAll(list);
        this.notifyDataSetChanged();
    }
}

Puedes probar. Funciona para mi

Dũng Phạm Tiến
fuente
1

Si adapterse establece en, AutoCompleteTextViewentonces notifyDataSetChanged()no funciona.

Necesita esto para actualizar el adaptador:

myAutoCompleteAdapter = new ArrayAdapter<String>(MainActivity.this, 
        android.R.layout.simple_dropdown_item_1line, myList);

myAutoComplete.setAdapter(myAutoCompleteAdapter);

Consulte: http://android-er.blogspot.in/2012/10/autocompletetextview-with-dynamic.html

Prabs
fuente
1

Si por casualidad llegaste a este hilo y te preguntas por qué adapter.invaidate()o los adapter.clear()métodos no están presentes en tu caso, entonces tal vez porque podrías estar usando en RecyclerView.Adapterlugar de lo BaseAdapterque usa el que hace la pregunta. Si borra listo arraylistno resuelve su problema, puede suceder que esté haciendo dos o más instancias deladapter por ejemplo:

Actividad principal

...

adapter = new CustomAdapter(list);
adapter.notifyDataSetChanged();
recyclerView.setAdapter(adapter);

...

y
algún fragmento

...

adapter = new CustomAdapter(newList);
adapter.notifyDataSetChanged();

...

Si en el segundo caso espera un cambio en la lista de vistas infladas en la vista del reciclador, entonces no sucederá, ya que en la segunda vez adapterse crea una nueva instancia de la que no está adjunta a la vista del reciclador. La configuración notifyDataSetChangeden el segundo adaptador no cambiará el contenido de la vista de reciclador. Para ello, cree una nueva instancia de la vista de reciclador en SomeFragment y adjúntela a la nueva instancia del adaptador.

Algún fragmento

...

recyclerView = new RecyclerView();
adapter = new CustomAdapter();
recyclerView.setAdapter(adapter);

...

Sin embargo, no recomiendo hacer varias instancias del mismo adaptador y vista de reciclador.

Neeraj Sewani
fuente
0

Agrega este código

runOnUiThread(new Runnable() { public void run() {
               adapter = new CustomAdapter(anotherdata);
            adapter.notifyDataSetChanged();
            }
        });
ShriKant A
fuente
0

¡Tengo el mismo problema pero lo acabo de terminar!

deberías cambiar a

public class ReceiptListAdapter extends BaseAdapter {

    public List<Receipt> receiptlist;
    private Context context;
    private LayoutInflater inflater;
    private DateHelpers dateH;
    private List<ReceiptViewHolder> receiptviewlist;

    public ReceiptListAdapter(Activity activity, Context mcontext, List<Receipt> rl) {
        context = mcontext;
        receiptlist = rl;
        receiptviewlist = new ArrayList<>();
        receiptviewlist.clear();
        for(int i = 0; i < receiptlist.size(); i++){
          ReceiptViewHolder receiptviewholder = new ReceiptViewHolder();
          receiptviewlist.add(receiptviewholder);
        }
        Collections.reverse(receiptlist);
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        dateH = new DateHelpers();
    }

    @Override
    public int getCount() {
        try {
            int size = receiptlist.size();
            return size;
        } catch(NullPointerException ex) {
            return 0;
        }
    }

    public void updateReceiptsList(List<Receipt> newlist) {
        receiptlist = newlist;
        this.notifyDataSetChanged();
    }

    @Override
    public Receipt getItem(int i) {
        return receiptlist.get(i);
    }

    @Override
    public long getItemId(int i) {
        return receiptlist.get(i).getReceiptId() ;
    }

    private String getPuntenString(Receipt r) {
        if(r.getPoints().equals("1")) {
            return "1 punt";
        }
        return r.getPoints()+" punten";
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi=convertView;

        final Receipt receipt = receiptlist.get(position);
        ReceiptViewHolder receiptviewholder;
        Typeface tf_hn = Typeface.createFromAsset(context.getAssets(), "helveticaneue.ttf");        
        Typeface tf_hn_bold = Typeface.createFromAsset(context.getAssets(), "helveticaneuebd.ttf");

        if (vi == null) { //convertview==null
            ReceiptViewHolder receiptviewholder = receiptviewlist.get(position);
            vi = inflater.inflate(R.layout.view_listitem_receipt, null);
            vi.setOnClickListener(null);
            vi.setOnLongClickListener(null);
            vi.setLongClickable(false);
            receiptviewholder.shop = (TextView) vi.findViewById(R.id.tv_listitemreceipt_shop);
            receiptviewholder.date = (TextView) vi.findViewById(R.id.tv_listitemreceipt_date);
            receiptviewholder.price = (TextView) vi.findViewById(R.id.tv_listitemreceipt_price);
            receiptviewholder.points = (TextView) vi.findViewById(R.id.tv_listitemreceipt_points);
            receiptviewholder.shop.setTypeface(tf_hn_bold);
            receiptviewholder.price.setTypeface(tf_hn_bold);
            vi.setTag(receiptviewholder);
        }else{//convertview is not null
            receiptviewholder = (ReceiptViewHolder)vi.getTag();
        }

        receiptviewholder.shop.setText(receipt.getShop());
        receiptviewholder.date.setText(dateH.timestampToDateString(Long.parseLong(receipt.getPurchaseDate())));
        receiptviewholder.price.setText("€ "+receipt.getPrice());
        receiptviewholder.points.setText(getPuntenString(receipt));

        vi.setClickable(false);
        return vi;
    }

    public static class ReceiptViewHolder {
        public TextView shop;
        public TextView date;
        public TextView price;
        public TextView points;
    }

    public Object getFilter() {
        // XXX Auto-generated method stub
        return null;
    }

}
林 權 章
fuente
0

Mi caso era diferente pero podría ser el mismo para otros.

para aquellos que todavía no pudieron encontrar una solución y probaron todo lo anterior, si está utilizando el adaptador dentro del fragmento, entonces la razón no está funcionando fragment could be recreating so the adapter is recreating everytime the fragment recreate

debe verificar si el adaptador y la lista de objetos son nulos antes de inicializar

if(adapter == null){
  adapter = new CustomListAdapter(...);
}
...

if(objects == null){
  objects = new ArrayList<>();
}

Farido mastr
fuente