Android ListView no se actualiza después de notifyDataSetChanged

116

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

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

Pero esto no actualiza el ListView. Incluso después de reiniciar la aplicación, los elementos actualizados no se muestran. Mi se ItemAdapterextiendeBaseAdapter

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

¿Puede alguien ayudar, por favor?

Descifrador
fuente
2
¿Ha probado para ver si la operación de la base de datos está funcionando correctamente? ¿Cómo se ve el adaptador? Además, si crea un objeto on para la adapterreferencia, ¿por qué lo prueba para nulo una línea a continuación?
Luksprog
El código no arroja una excepción y lo verifiqué usando depuración. Todos los métodos se ejecutan sin error. Sí, eso es un error tonto.
Codificador

Respuestas:

229

Mira tu onResumemétodo en ItemFragment:

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

lo que acaba de actualizar antes de llamar notifyDataSetChanged()no es el campo del adaptador, private List<Item> items;sino el campo idénticamente declarado del fragmento. El adaptador todavía almacena una referencia a la lista de elementos que pasó cuando creó el adaptador (por ejemplo, en onCreate del fragmento). La forma más corta (en el sentido de la cantidad de cambios) pero no elegante de hacer que su código se comporte como espera es simplemente reemplazar la línea:

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

con

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

Una solución más elegante:

1) eliminar elementos private List<Item> items;de ItemFragment: debemos mantener la referencia a ellos solo en el adaptador

2) cambie onCreate a:

@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) agregue el método en ItemAdapter:

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

4) cambie su onResume a:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Tomasz Gawel
fuente
¿No sería más limpio mover todo el asunto dbHelper al adaptador? Así que solo llamarías adapter.swapItems();y el adaptador haría las dbHelper.getItems()cosas. Pero de todos modos gracias por la respuesta :)
Ansgar
7
¿Por qué debería borrar () y agregar los elementos nuevamente? ¿No es ese exactamente el propósito de notifyDataSetChanged()?
Phil Ryan
1
@tomsaz, ¿pueden ayudarme con esto? stackoverflow.com/questions/28148618/…
1
Gracias @tomsaz Gawel, sus swapItems realmente me ayudan mucho, no sé por qué mi adapter.notifydatasetchanged no funciona, ya que la "lista" que estoy pasando también está actualizada, incluso yo la he comprobado imprimiendo el registro. ¿Puede explicarme esto? concepto
Kimmi Dhingra
1
Esta respuesta es correcta. El problema es que la ArrayList de elementos del ADAPTADOR no se estaba actualizando. Esto significa que puede llamar a notifydatasetchanged hasta que su cara esté azul sin ningún efecto. El adaptador actualiza su conjunto de datos con el mismo conjunto de datos, por lo que NO hay cambios. Otra alternativa a la solución publicada en esta respuesta que podría ser más limpia es: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li
23

Está asignando elementos recargados a elementos de variables globales en onResume(), pero esto no se reflejará en la ItemAdapterclase, porque tiene su propia variable de instancia llamada 'elementos'.

Para actualizar ListView, agregue un refresh () en la ItemAdapterclase que acepta datos de lista, es decir, elementos

class ItemAdapter
{
    .....

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

actualizar onResume()con el siguiente código

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
fuente
1
Esto es exactamente correcto. El constructor del adaptador espera que se le pasen elementos, pero solo actualiza el campo de la clase externa.
LuxuryMode
Hola Santhosh. ¿Puede echar un vistazo a un problema similar: stackoverflow.com/questions/35850715/…
8

En onResume () cambia esta línea

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

a

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

El problema es que nunca le dice a su adaptador sobre la lista de elementos nuevos. Si no desea pasar una nueva lista a su adaptador (ya que parece que no lo hace), use items.addAlldespués de su clear(). Esto asegurará que está modificando la misma lista a la que hace referencia el adaptador.

Justin Breitfeller
fuente
Es confuso que adapter.clear()no obliga al adaptador de darse cuenta de que debe refrescar la vista, pero adapter.add()o adapter.addAll()lo hace. ¡Gracias por la respuesta!
w3bshark
Tenga en cuenta que estaba usando items.addAll()y no adaptor.addAll (). Lo único que permite que el adaptador reaccione a los cambios es el notifyDataSetChanged. La razón por la que el adaptador ve cambios es que la itemslista es la misma lista que está usando el adaptador.
Justin Breitfeller
4

Si el adaptador ya está configurado, volver a configurarlo no actualizará la vista de lista. En su lugar, primero verifique si la vista de lista tiene un adaptador y luego llame al método apropiado.

Creo que no es muy buena idea crear una nueva instancia del adaptador mientras configura la vista de lista. En su lugar, cree un objeto.

BuildingAdapter adapter = new BuildingAdapter(context);

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

También puede intentar ejecutar la lista de actualización en el hilo de la interfaz de usuario:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
fuente
No estoy seguro de cómo implementar el hilo de la interfaz de usuario. Mi actividad principal tiene 3 fragmentos (pestañas) y el código de la pregunta está relacionado con uno de los fragmentos que contiene la vista de lista. La razón para pasar elementos a ItemAdapteres que quiero colorear las filas y la vista de lista muestra varios elementos de datos. He publicado el código del adaptador.
Codificador
Necesita poner su código que completa su lista en mi código de ejemplo usando "esto". en lugar de "actividad"
AlexGo
En algunos casos, no se actualiza cuando se ejecuta notifyDataSetChanged () en un hilo diferente, por lo que la solución anterior es adecuada para algunos casos.
Ayman Al-Absi
4

Si desea actualizar su vista de lista, no importa si desea hacerlo en onResume(), onCreate()o en alguna otra función, lo primero que debe tener en cuenta es que no necesitará crear una nueva instancia del adaptador, solo complete las matrices con sus datos nuevamente. La idea es algo similar a esto:

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

Entonces, dependiendo de esa respuesta, debe usar la misma List<Item>en su Fragment. En la primera inicialización de su adaptador, complete su lista con los elementos y configure el adaptador en su vista de lista. Después de eso, en cada cambio en sus elementos, debe borrar los valores del archivo principal List<Item> itemsy luego completarlo nuevamente con sus nuevos elementos y llamar notifySetDataChanged();.

Asi es como funciona : ).

h4rd4r7c0r3
fuente
Gracias por la respuesta. Hice los cambios que mencionaste. He publicado mi código. Todavía no funciona. Ahora ni siquiera muestra la vista de lista cuando se agregan nuevos elementos.
Coder
He cambiado el código. Lo extraño de observar es que el elemento no se actualiza en DB
Coder
Este hilo es para la base de datos stackoverflow.com/questions/14555332/…
Codificador
3

Una respuesta de AlexGo hizo el truco para mí:

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

List Update funcionó para mí antes cuando la actualización se activó desde un evento de GUI, por lo que estaba en el hilo de la interfaz de usuario.

Sin embargo, cuando actualizo la lista desde otro evento / hilo, es decir, una llamada desde fuera de la aplicación, la actualización no estaría en el hilo de la interfaz de usuario e ignoró la llamada a getListView. Llamar a la actualización con runOnUiThread como arriba funcionó para mí. ¡¡Gracias!!

usuario2996950
fuente
3

Prueba esto

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

Pruebe esto en el onPause()método de la clase Activity.

Som
fuente
1
adapter.setNotifyDataChanged()

debería hacer el truco.

Sicario
fuente
3
donde poner esta la pregunta aqui ??
swiftBoy
1

Si su lista está contenida en el propio Adaptador, llamar a la función que actualiza la lista también debería llamar notifyDataSetChanged().

Ejecutar esta función desde el subproceso de la interfaz de usuario hizo el truco para mí:

La refresh()función dentro del Adaptador

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

Luego, a su vez, ejecute esta función desde el hilo de la interfaz de usuario

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Dévan Coetzee
fuente
De hecho, esto marcó una diferencia para mí, ya que la actualización se realizó a través de la red a través de un hilo diferente.
Chuck
0

Intente así:

this.notifyDataSetChanged();

en vez de:

adapter.notifyDataSetChanged();

Tienes que notifyDataSetChanged()al ListViewno a la clase adaptador.

Jachu
fuente
por supuesto que no lo hará, la única posibilidad si la actividad se amplía con una vista de lista
cmario