web-dev-qa-db-ja.com

notifyDataSetChangedがRecyclerViewで機能しない

サーバーからデータを取得し、それを解析してリストに保存しています。私はこのリストをRecyclerViewのアダプターに使用しています。フラグメントを使用しています。

KitKatでNexus 5を使用しています。このためにサポートライブラリを使用しています。これは違いを生むでしょうか?

ここに私のコードがあります:(質問にダミーデータを使用)

メンバー変数:

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

RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;

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

サーバーからデータを取得した後、parseResponse()が呼び出されます。

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

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

しかし、ビューにデータが表示されません。私は何を間違えていますか?

これが私のログです

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

この後ログを取得しません。アダプタのgetItemCount()を再度呼び出すべきではありませんか?

48
Vamsi Challa

parseResponse()BusinessAdapterクラスの新しいインスタンスを作成していますが、実際にはどこでも使用していないため、RecyclerViewは新しいインスタンスが存在することを知りません。

次のいずれかが必要です。

  • recyclerView.setAdapter(mBusinessAdapter)を再度呼び出して、RecyclerViewのアダプター参照を更新して、新しいものを指すようにします
  • または、mBusinessAdapter = new BusinessAdapter(mBusinesses);を削除して、既存のアダプターを引き続き使用します。 mBusinesses参照を変更していないため、アダプターは引き続きその配列リストを使用し、notifyDataSetChanged()を呼び出すと正しく更新されるはずです。
63
Tanis.7x

この方法を試してください:

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

少し時間がかかりますが、動作するはずです。

24
NiVeR

同じ問題がありました。クラスのadapterの前にonCreate publicを宣言して解決しました。

PostAdapter postAdapter;

その後

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

最後に私は電話しました:

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

これがあなたを助けますように。

私は誰もこれをここで言及したとは思わないので、他の答えを補足するだけです:notifyDataSetChanged()メインスレッドで実行されるべきです(その他notify<Something>もちろんRecyclerView.Adapterのメソッドも)

私が収集したものから、同じブロック内に解析プロシージャとnotifyDataSetChanged()の呼び出しがあるため、ワーカースレッドから呼び出すか、メインスレッドでJSON解析を実行しています(これもいいえ、あなたが知っていると確信しています)。したがって、適切な方法は次のとおりです。

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奇妙なことに、IDEまたはランタイムログのいずれかからメインスレッドの事柄に関する兆候を得るとは思わない。これは私の個人的な観察からです:notifyDataSetChanged()をワーカースレッドから呼び出すと、義務を取得できませんビュー階層を作成した元のスレッドのみがそのビューに触れることができますメッセージまたはそのようなもの-静かに失敗します(私の場合、1つのオフメインスレッド呼び出しは、おそらく何らかの競合状態が原因で、後続のメインスレッド呼び出しが適切に機能しなくなることさえあります)

さらに、 RecyclerView.Adapter api reference も関連する公式の dev guide も、現時点(2017年)のメインスレッド要件を明示的に言及しておらず、Androidスタジオのリント検査ルールもこの問題に関係しているようです。

しかし、 ここに説明があります 著者自身による

2
Ivan Bartsov

古いビューモデルをクリアして、新しいデータをアダプターに設定し、notifyDataSetChanged()を呼び出します

1
pradeep kumar