Android ListView não atualiza após notificarDataSetChanged

116

Meu código 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);
        }
    }
}

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>

Mas isso não atualiza o ListView. Mesmo depois de reiniciar o aplicativo, os itens atualizados não são exibidos. Meu ItemAdapterestendeBaseAdapter

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

Alguém pode ajudar por favor?

Coder
fonte
2
Você testou para ver se a operação do banco de dados está funcionando corretamente? Como é o adaptador? Além disso, se você criar um objeto on para a adapterreferência, por que testá-lo para null uma linha abaixo?
Luksprog
O código não lança exceção e eu verifiquei usando depuração. Todos os métodos executados sem erros. Sim, isso é um erro bobo.
Coder

Respostas:

229

Observe o seu onResumemétodo em ItemFragment:

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

o que você acabou de atualizar antes de chamar notifyDataSetChanged()não é o campo do adaptador, private List<Item> items;mas o campo declarado de forma idêntica do fragmento. O adaptador ainda armazena uma referência à lista de itens que você passou quando criou o adaptador (por exemplo, no fragmento onCreate). A maneira mais curta (em termos de número de mudanças), mas não elegante, de fazer seu código se comportar como você espera é simplesmente substituir a linha:

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

com

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

Uma solução mais elegante:

1) remover itens private List<Item> items;de ItemFragment- precisamos manter referência a eles apenas no adaptador

2) alterar onCreate para:

@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) adicionar método no ItemAdapter:

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

4) mude seu onResume para:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Tomasz Gawel
fonte
Não seria mais limpo mover todo o dbHelper para o adaptador? Então, você apenas ligaria adapter.swapItems();e o adaptador faria as dbHelper.getItems()coisas. De qualquer forma, obrigado pela resposta :)
Ansgar
7
Por que você deve limpar () e adicionar os itens novamente? Não é exatamente esse o propósito de notifyDataSetChanged()?
Phil Ryan
1
@tomsaz, você pode me ajudar com stackoverflow.com/questions/28148618/…
1
Obrigado @tomsaz Gawel, seus swapItems realmente me ajudaram muito, não sei por que meu adaptador.notifydatasetchanged não funciona, já que a "lista" que estou passando também está atualizada, mesmo eu a verifiquei imprimindo log, Você pode me explicar isso conceito
Kimmi Dhingra
1
Esta resposta está correta. O problema é que a ArrayList do item do ADAPTADOR não estava sendo atualizada. Isso significa que você pode chamar NoticeDatasetchanged até que seu rosto fique azul sem qualquer efeito. O conector está atualizando seu conjunto de dados com o mesmo conjunto de dados, portanto, NÃO há alterações. Outra alternativa para a solução postada nesta resposta que pode ser mais limpa é: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li
23

Você está atribuindo itens recarregados a itens de variáveis ​​globais em onResume(), mas isso não se refletirá na ItemAdapterclasse, porque ela tem sua própria variável de instância chamada 'itens'.

Para atualizar ListView, adicione um refresh () na ItemAdapterclasse que aceita dados de lista, ou seja, itens

class ItemAdapter
{
    .....

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

atualizar onResume()com o seguinte código

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
fonte
1
Isso é exatamente correto. O construtor do adaptador espera receber itens, mas ele apenas atualiza o campo da classe externa.
LuxuryMode
Oi Santhosh. Você pode dar uma olhada em um problema semelhante: stackoverflow.com/questions/35850715/…
8

Em onResume () altere esta linha

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

para

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

O problema é que você nunca informa ao adaptador sobre a nova lista de itens. Se você não quiser passar uma nova lista para o seu adaptador (parece que não), use items.addAlldepois do seu clear(). Isso garantirá que você esteja modificando a mesma lista à qual o adaptador faz referência.

Justin Breitfeller
fonte
É confuso que adapter.clear()não força o adaptador a perceber que a visualização deve ser atualizada, mas adapter.add()ou adapter.addAll()faz. Obrigado pela resposta!
w3bshark 01 de
Observe que eu estava usando, items.addAll()e não adapter.addAll (). A única coisa que permite que o adaptador reaja às mudanças é o notifyDataSetChanged. O motivo pelo qual o adaptador vê as alterações é que a itemslista é a mesma que o adaptador está usando.
Justin Breitfeller
4

Se o adaptador já estiver definido, configurá-lo novamente não atualizará a visualização da lista. Em vez disso, verifique primeiro se o listview tem um adaptador e, em seguida, chame o método apropriado.

Acho que não é uma ideia muito boa criar uma nova instância do adaptador enquanto define a exibição de lista. Em vez disso, crie um objeto.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Além disso, você pode tentar executar a lista de atualização no UI Thread:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
fonte
Não tenho certeza de como implementar o thread de interface do usuário. Minha atividade principal tem 3 fragmentos (guias) e o código em questão está relacionado a um dos fragmentos que contém a exibição de lista. O motivo de passar itens para ItemAdapteré que eu quero colorir as linhas e a exibição de lista exibe vários itens de dados. Publiquei o código do adaptador.
Coder
Você precisa colocar o código que preenche sua lista no meu código de exemplo usando "this". em vez de "atividade"
AlexGo
Em alguns casos, ele não é atualizado quando você executa notificarDataSetChanged () em um encadeamento diferente, portanto, a solução acima é a certa para alguns casos.
Ayman Al-Absi
4

Se você deseja atualizar sua visualização de lista, não importa se deseja fazer isso em onResume(), onCreate()ou em alguma outra função, a primeira coisa que você deve perceber é que você não precisará criar uma nova instância do adaptador, apenas preencher os arrays com seus dados novamente. A ideia é algo semelhante a esta:

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

Portanto, dependendo dessa resposta, você deve usar a mesma List<Item>na sua Fragment. Em sua primeira inicialização do adaptador, você preenche sua lista com os itens e define o adaptador para seu listview. Depois disso, em cada mudança em seus itens, você deve limpar os valores do principal List<Item> itemse então preenchê-los novamente com seus novos itens e chamar notifySetDataChanged();.

É assim que funciona : ).

h4rd4r7c0r3
fonte
Obrigado pela resposta. Eu fiz as mudanças como você mencionou. Eu postei meu código. Ainda não funciona. Agora ele nem mostra a exibição de lista quando novos itens são adicionados.
Coder
Eu mudei o código. O estranho de se observar é que o item não está sendo atualizado no DB
Coder
Este tópico é para o banco de dados stackoverflow.com/questions/14555332/…
Coder
3

Uma resposta do AlexGo funcionou para mim:

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

A atualização de lista funcionou para mim antes, quando a atualização foi disparada a partir de um evento da GUI, estando, portanto, no thread de interface do usuário.

No entanto, quando eu atualizo a lista de outro evento / thread - ou seja, uma chamada de fora do aplicativo, a atualização não estaria no thread de IU e ele ignorou a chamada para getListView. Chamar a atualização com runOnUiThread como acima funcionou para mim. Obrigado!!

user2996950
fonte
3

Tente isto

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

Tente isso no onPause()método da classe Activity.

Som
fonte
1
adapter.setNotifyDataChanged()

deve fazer o truque.

assassino de aluguel
fonte
3
onde colocar é a questão aqui ??
swiftBoy
1

Se sua lista estiver contida no próprio conector, chamar a função que atualiza a lista também deve ser chamada notifyDataSetChanged().

Executar esta função a partir do UI Thread funcionou para mim:

A refresh()função dentro do adaptador

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

Então, por sua vez, execute esta função a partir do UI Thread

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Dévan Coetzee
fonte
Isso realmente fez uma diferença para mim, pois a atualização veio pela rede por meio de um thread diferente.
Chuck
0

Tente assim:

this.notifyDataSetChanged();

ao invés de:

adapter.notifyDataSetChanged();

Você tem que notifyDataSetChanged()fazer o ListViewnão para a classe do adaptador.

Jachu
fonte
claro que não, a única chance se a atividade for estendida por um listview
cmario