Android ListView ne se rafraîchit pas après notifyDataSetChanged

116

Ma listeCode de fragmentation

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

Ma liste

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

Mais cela ne rafraîchit pas le fichier ListView. Même après le redémarrage de l'application, les éléments mis à jour ne sont pas affichés. Mon ItemAdapterétendBaseAdapter

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

Quelqu'un peut-il aider s'il vous plaît?

Codeur
la source
2
Avez-vous testé pour voir si le fonctionnement de la base de données fonctionne correctement? À quoi ressemble l'adaptateur? De plus, si vous créez un objet on pour la adapterréférence, pourquoi le testez-vous pour null une ligne ci-dessous?
Luksprog
Le code ne lève pas d'exception et j'ai vérifié en utilisant le débogage. Toutes les méthodes exécutées sans erreur. Ouais c'est une erreur stupide.
Coder

Réponses:

229

Regardez votre onResumeméthode dans ItemFragment:

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

ce que vous venez de mettre à jour avant d'appeler notifyDataSetChanged()n'est pas le champ de l'adaptateur private List<Item> items;mais le champ déclaré à l'identique du fragment. L'adaptateur stocke toujours une référence à la liste des éléments que vous avez transmis lors de la création de l'adaptateur (par exemple dans onCreate de fragment). Le moyen le plus court (en termes de nombre de modifications) mais non élégant de faire en sorte que votre code se comporte comme prévu est simplement de remplacer la ligne:

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

avec

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

Une solution plus élégante:

1) supprimer des éléments private List<Item> items;de ItemFragment- nous devons garder la référence à eux uniquement dans l'adaptateur

2) remplacez onCreate par:

@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) ajouter une méthode dans ItemAdapter:

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

4) Changez votre onResume en:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Tomasz Gawel
la source
Ne serait-il pas plus propre de déplacer l'ensemble de dbHelper dans l'adaptateur? Vous n'appeliez donc que adapter.swapItems();et l'adaptateur ferait le dbHelper.getItems()nécessaire. Mais de toute façon merci pour la réponse :)
Ansgar
7
Pourquoi devriez-vous effacer () et ajouter à nouveau les éléments? N'est-ce pas exactement le but de notifyDataSetChanged()?
Phil Ryan
1
@tomsaz pouvez-vous m'aider avec ce stackoverflow.com/questions/28148618/…
1
Merci @tomsaz Gawel, vos swapItems m'aident vraiment beaucoup, je ne sais pas pourquoi mon adapter.notifydatasetchanged ne fonctionne pas, car la "liste" que je passe est également mise à jour, même si je l'ai vérifiée en imprimant le journal, pouvez-vous s'il vous plaît m'expliquer ceci concept
Kimmi Dhingra
1
Cette réponse est correcte. Le problème est que la liste des éléments de l'ADAPTATEUR n'était pas mise à jour. Cela signifie que vous pouvez appeler notifydatasetchanged jusqu'à ce que votre visage soit bleu sans aucun effet. L'adaptateur met à jour votre ensemble de données avec le même ensemble de données, il n'y a donc AUCUN changement. Une autre alternative à la solution publiée dans cette réponse qui pourrait être plus propre est: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li
23

Vous affectez des éléments rechargés à des éléments de variable globale dans onResume(), mais cela ne se reflétera pas dans la ItemAdapterclasse, car il a sa propre variable d'instance appelée «éléments».

Pour rafraîchir ListView, ajoutez un refresh () dans la ItemAdapterclasse qui accepte les données de liste, c'est-à-dire les éléments

class ItemAdapter
{
    .....

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

mettre onResume()à jour avec le code suivant

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
la source
1
C'est exactement ça. Le constructeur de l'adaptateur s'attend à recevoir des éléments, mais il ne met à jour que le champ de la classe externe.
LuxuryMode
Salut Santhosh. Pouvez-vous jeter un oeil à un problème similaire: stackoverflow.com/questions/35850715/…
8

Dans onResume () changez cette ligne

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

à

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

Le problème est que vous ne communiquez jamais à votre adaptateur la liste des nouveaux éléments. Si vous ne voulez pas transmettre une nouvelle liste à votre adaptateur (comme il semble que vous ne le faites pas), utilisez simplement items.addAllaprès votre clear(). Cela garantira que vous modifiez la même liste à laquelle l'adaptateur fait référence.

Justin Breitfeller
la source
Il est déroutant de adapter.clear()ne pas forcer l'adaptateur à se rendre compte que la vue doit être actualisée, mais adapter.add()ou le adapter.addAll()fait. Merci pour la réponse!
w3bshark
Notez que j'utilisais items.addAll()et non adapter.addAll (). La seule chose qui laisse l'adaptateur réagir aux changements est le notifyDataSetChanged. La raison pour laquelle l'adaptateur voit des modifications est que la itemsliste est la même que celle utilisée par l'adaptateur.
Justin Breitfeller
4

Si l'adaptateur est déjà défini, le redéfinir n'actualisera pas la liste. Au lieu de cela, vérifiez d'abord si la vue de liste a un adaptateur, puis appelez la méthode appropriée.

Je pense que ce n'est pas une très bonne idée de créer une nouvelle instance de l'adaptateur tout en définissant la vue de liste. Au lieu de cela, créez un objet.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Vous pouvez également essayer d'exécuter la liste d'actualisation sur UI Thread:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
la source
Je ne sais pas comment implémenter le fil de l'interface utilisateur. Mon activité principale comporte 3 fragments (onglets) et le code de la question est lié à l'un des fragments qui contient la vue de liste. La raison des articles de passer à ItemAdapterest que je veux colorer les lignes et la vue de la liste affiche plusieurs éléments de données. J'ai posté le code de l'adaptateur.
Coder
Vous devez mettre votre code qui remplit votre liste dans mon exemple de code en utilisant "ceci". au lieu de "activité"
AlexGo
Dans certains cas, il n'est pas mis à jour lorsque vous exécutez le notifyDataSetChanged () dans un thread différent, donc la solution ci-dessus convient dans certains cas.
Ayman Al-Absi
4

Si vous souhaitez mettre à jour votre liste, peu importe si vous souhaitez le faire sur onResume(), onCreate()ou dans une autre fonction, la première chose que vous devez réaliser est que vous n'aurez pas besoin de créer une nouvelle instance de l'adaptateur, il suffit de remplir les tableaux avec vos données à nouveau. L'idée est quelque chose de similaire à ceci:

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

Donc, en fonction de cette réponse, vous devez utiliser la même chose List<Item>dans votre fichier Fragment. Lors de votre première initialisation d'adaptateur, vous remplissez votre liste avec les éléments et définissez l'adaptateur sur votre liste. Après cela, à chaque modification de vos éléments, vous devez effacer les valeurs du principal List<Item> itemset le remplir à nouveau avec vos nouveaux éléments et appeler notifySetDataChanged();.

Voilà comment ça marche :).

h4rd4r7c0r3
la source
Merci pour la réponse. J'ai fait les changements comme vous l'avez mentionné. J'ai posté mon code. Cela ne fonctionne toujours pas. Maintenant, il n'affiche même pas la vue de liste lorsque de nouveaux éléments sont ajoutés.
Coder
J'ai changé le code. La chose étrange à observer est que l'élément n'est pas mis à jour dans DB
Coder
Ce fil est pour la base de données stackoverflow.com/questions/14555332/…
Coder
3

Une réponse d'AlexGo a fait l'affaire pour moi:

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

La mise à jour de la liste a fonctionné pour moi auparavant lorsque la mise à jour a été déclenchée à partir d'un événement d'interface graphique, étant ainsi dans le thread d'interface utilisateur.

Cependant, lorsque je mets à jour la liste à partir d'un autre événement / thread - c'est-à-dire un appel de l'extérieur de l'application, la mise à jour ne serait pas dans le thread d'interface utilisateur et elle a ignoré l'appel à getListView. L'appel de la mise à jour avec runOnUiThread comme ci-dessus a fait l'affaire pour moi. Merci!!

user2996950
la source
3

Essaye ça

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

Essayez ceci dans la onPause()méthode de la classe d'activité.

Som
la source
1
adapter.setNotifyDataChanged()

devrait faire l'affaire.

Tueur à gages
la source
3
où poser est la question ici ??
swiftBoy
1

Si votre liste est contenue dans l'adaptateur lui-même, l'appel de la fonction qui met à jour la liste doit également appeler notifyDataSetChanged().

L'exécution de cette fonction à partir du fil UI a fait l'affaire pour moi:

La refresh()fonction à l'intérieur de l'adaptateur

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

Ensuite, exécutez à son tour cette fonction à partir du fil de discussion de l'interface utilisateur

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Dévan Coetzee
la source
Cela a en effet fait une différence pour moi car la mise à jour est venue sur le réseau via un fil différent.
Chuck
0

Essayez comme ceci:

this.notifyDataSetChanged();

au lieu de:

adapter.notifyDataSetChanged();

Vous devez notifyDataSetChanged()le ListViewpas à la classe d'adaptation.

Jachu
la source
bien sûr que ce ne sera pas le cas, la seule chance si l'activité est prolongée par une liste
cmario