web-dev-qa-db-fra.com

notifyDataSetChanged ne fonctionne pas sur RecyclerView

Je reçois des données du serveur, puis les analyse et les stocke dans une liste. J'utilise cette liste pour l'adaptateur de RecyclerView. J'utilise des fragments.

J'utilise un Nexus 5 avec KitKat. J'utilise la bibliothèque de support pour cela. Cela fera-t-il une différence?

Voici mon code: (Utilisation de données factices pour la question)

Variables membres:

List<Business> mBusinesses = new ArrayList<Business>();

RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;

Mon onCreateView():

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    // Getting data from server
    getBusinessesDataFromServer();

    View view = inflater.inflate(R.layout.fragment_business_list,
            container, false);
    recyclerView = (RecyclerView) view
            .findViewById(R.id.business_recycler_view);
    recyclerView.setHasFixedSize(true);

    mLayoutManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(mLayoutManager);

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    recyclerView.setAdapter(mBusinessAdapter);

    return view;
}

Après avoir obtenu les données du serveur, la fonction parseResponse() est appelée.

protected void parseResponse(JSONArray response, String url) {
    // insert dummy data for demo

    mBusinesses.clear();

    Business business;

    business = new Business();
    business.setName("Google");
    business.setDescription("Google HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Yahoo");
    business.setDescription("Yahoo HeadQuaters");
    mBusinesses.add(business);

    business = new Business();
    business.setName("Microsoft");
    business.setDescription("Microsoft HeadQuaters");
    mBusinesses.add(business);

    Log.d(Const.DEBUG, "Dummy Data Inserted\nBusinesses Length: "
            + mBusinesses.size());

    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    mBusinessAdapter.notifyDataSetChanged();
}

Mon BusinessAdapter:

public class BusinessAdapter extends
    RecyclerView.Adapter<BusinessAdapter.ViewHolder> {

    private List<Business> mBusinesses = new ArrayList<Business>();

    // Provide a reference to the type of views that you are using
    // (custom viewholder)
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextViewName;
        public TextView mTextViewDescription;
        public ImageView mImageViewLogo;

        public ViewHolder(View v) {
            super(v);
            mTextViewName = (TextView) v
                    .findViewById(R.id.textView_company_name);
            mTextViewDescription = (TextView) v
                    .findViewById(R.id.textView_company_description);
            mImageViewLogo = (ImageView) v
                    .findViewById(R.id.imageView_company_logo);
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public BusinessAdapter(List<Business> myBusinesses) {

        Log.d(Const.DEBUG, "BusinessAdapter -> constructor");

        mBusinesses = myBusinesses;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public BusinessAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
            int viewType) {

        Log.d(Const.DEBUG, "BusinessAdapter -> onCreateViewHolder()");

        // create a new view
        View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_business_list, parent, false);

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element

        Log.d(Const.DEBUG, "BusinessAdapter -> onBindViewHolder()");

        Business item = mBusinesses.get(position);
        holder.mTextViewName.setText(item.getName());
        holder.mTextViewDescription.setText(item.getDescription());
        holder.mImageViewLogo.setImageResource(R.drawable.ic_launcher);

    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {

        Log.d(Const.DEBUG, "BusinessAdapter -> getItemCount()");

        if (mBusinesses != null) {
            Log.d(Const.DEBUG, "mBusinesses Count: " + mBusinesses.size());
            return mBusinesses.size();
        }
        return 0;
    }
}

Mais je ne reçois pas les données affichées dans la vue. Qu'est-ce que je fais mal?

Voici mon journal,

07-14 21:15:35.669: D/xxx(2259): Dummy Data Inserted
07-14 21:15:35.669: D/xxx(2259): Businesses Length: 3
07-14 21:26:26.969: D/xxx(2732): BusinessAdapter -> constructor

Je ne reçois pas de journaux après cela. getItemCount() dans l'adaptateur ne devrait-il pas être appelé à nouveau?

48
Vamsi Challa

Dans votre parseResponse(), vous créez une nouvelle instance de la classe BusinessAdapter, mais vous ne l'utilisez réellement nulle part. Votre RecyclerView ne connaît donc pas la nouvelle instance. existe.

Vous devez soit:

  • Appelez à nouveau recyclerView.setAdapter(mBusinessAdapter) pour mettre à jour la référence de l'adaptateur de RecyclerView afin qu'elle pointe vers votre nouvelle
  • Ou bien supprimez simplement mBusinessAdapter = new BusinessAdapter(mBusinesses); pour continuer à utiliser l'adaptateur existant. Puisque vous n'avez pas changé la référence mBusinesses, l'adaptateur utilisera toujours cette liste de tableaux et devrait se mettre à jour correctement lorsque vous appelez notifyDataSetChanged().
63
Tanis.7x

Essayez cette méthode:

List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification

cela prend un peu de temps, mais ça devrait marcher.

24
NiVeR

J'ai eu le même problème. Je viens de le résoudre en déclarant adapter public avant onCreate de la classe.

PostAdapter postAdapter;

après ça

postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);

enfin j'ai appelé:

@Override
protected void onPostExecute(Void aVoid) {
    super.onPostExecute(aVoid);
    // Display the size of your ArrayList
    Log.i("TAG", "Size : " + posts.size());
    progressBar.setVisibility(View.GONE);
    postAdapter.notifyDataSetChanged();
}

Puisse cela vous aider.

Juste pour compléter les autres réponses car je pense que personne ne l’a mentionné ici: notifyDataSetChanged() doit être exécuté sur le fil principal ( autres méthodes notify<Something> de RecyclerView.Adapter également, bien sûr)

D'après ce que je comprends, puisque vous avez les procédures d'analyse syntaxique et l'appel à notifyDataSetChanged() dans le même bloc, vous l'appelez à partir d'un thread de travail ou vous effectuez une analyse JSON sur le thread principal (qui est également un non-non car je suis sûr que vous le savez). Donc, la bonne façon serait:

protected void parseResponse(JSONArray response, String url) {
    // insert dummy data for demo
    // <yadda yadda yadda>
    mBusinessAdapter = new BusinessAdapter(mBusinesses);
    // or just use recyclerView.post() or [Fragment]getView().post()
    // instead, but make sure views haven't been destroyed while you were
    // parsing
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
            mBusinessAdapter.notifyDataSetChanged();
        }
    });

}

[~ # ~] ps [~ # ~] Ce qui est bizarre, c'est que je ne pense pas que vous obteniez des indications sur le sujet principal de IDE ou de run- journaux de temps. Cela vient juste de mes observations personnelles: si j'appelle notifyDataSetChanged() depuis un thread de travail, je ne reçois pas l'obligation Seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues message ou quelque chose du genre - cela échoue simplement en silence (et dans mon cas, un appel hors du fil principal peut même empêcher le bon fonctionnement des appels suivants du fil principal, probablement à cause d'une condition de concurrence).

De plus, ni RecyclerView.Adapter api reference , ni l'officiel concerné dev guide ne mentionnent explicitement l'exigence de thread principal pour le moment (le moment est 2017) et aucun des Android Les règles d'inspection de Studio lint semblent concerner ce problème non plus.

Mais, voici une explication de cela par l'auteur lui-même

2
Ivan Bartsov

Effacez votre ancien modèle de vue, définissez les nouvelles données sur l'adaptateur et appelez notifyDataSetChanged().

1
pradeep kumar