32

I want to click an item in recyclerView programmatically, I found a way do that:

recyclerView.findViewHolderForAdapterPosition(0).itemView.performClick();

but it doesn't work for me, the findViewHolderForAdapterPosition just returns null.

Did I miss something in my code?

HistoryListAdapter:

public class HistoryListAdapter extends RecyclerView.Adapter<HistoryListAdapter.ViewHolder> {
private static ArrayList<RecordItem> recordItems;
private static FragmentActivity activity;
private static RecordList recordList;

public HistoryListAdapter(ArrayList<RecordItem> recordItems, FragmentActivity FA, RecordList FRL) {
    this.recordItems = recordItems;
    this.activity = FA;
    this.recordList = FRL;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // create a new view
    View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.recorditem, parent, false);
    return new ViewHolder(itemLayoutView);
}

@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
    // TextView setText ...
}

// Return the size of the itemsData (invoked by the layout manager)
@Override
public int getItemCount() {
    return recordItems.size();
}

// inner class to hold a reference to each item of RecyclerView
public static class ViewHolder extends RecyclerView.ViewHolder{
    public TextView result, datetime;

    public ViewHolder(View itemLayoutView) {
        super(itemLayoutView);
        itemLayoutView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (activity != null) {
                    // do something...
                }
            }
        });
        result = (TextView) itemLayoutView.findViewById(R.id.result);
        heartrate = (TextView) itemLayoutView.findViewById(R.id.heartrate);
        datetime = (TextView) itemLayoutView.findViewById(R.id.datetime);
    }
}
}

RecordList:

RecyclerView listView = (RecyclerView) layoutView.findViewById(R.id.listView);
HistoryListAdapter listadapter = new HistoryListAdapter(itemsToShow, getActivity(), RecordList.this);
listView.swapAdapter(listadapter, false);
listView.findViewHolderForAdapterPosition(0).itemView.performClick();

I omitted some code, But it should not affect the overall structure of my code.

Community
  • 1
  • 1
Aaron Tsai
  • 514
  • 1
  • 6
  • 14
  • Check this answer out to get a nice way of implementing an onclick function in recyclerview : http://stackoverflow.com/questions/31174712/android-onclicklistener-for-recyclerviews-childs-child/31176508#31176508 – Smashing Sep 29 '15 at 06:22

6 Answers6

30

According to the official documentation:

if notifyDataSetChanged() has been called but the new layout has not been calculated yet, this method will return null since the new positions of views are unknown until the layout is calculated.

It's not safe to use findViewHolderForAdapterPosition().

While you call this method after listView.swapAdapter(listadapter, false); you'll always get a null as result because notifyDataSetChanged() will be called.

Rami
  • 7,879
  • 12
  • 36
  • 66
29

you can do this:

   listView.postDelayed(new Runnable()
            {
                @Override
                public void run()
                {
                    if(listView.findViewHolderForAdapterPosition(0)!=null )
                    {

                        listView.findViewHolderForAdapterPosition(0).itemView.performClick();
                    }
                }
            },50);
Dharmaraj
  • 1,256
  • 13
  • 12
  • 1
    Thanks @Dharmaraj. Works like charm. http://stackoverflow.com/a/30883242 as per yigit comments this happens because the position is not laid out (e.g. out of bounds or removed). – Faheem Kalsekar May 18 '17 at 12:02
  • 8
    Don't ever do it. Are you sure that 50 ms will be enough for old and slow devices? Ignore answers whenever you see`postDelayed` there (and it's not just about this question, I've been seen a lot of similar answers and comments to other questions). It's stupid... – user25 Mar 17 '19 at 22:34
  • 1
    @user25 Though I understand where you're coming from, calling `Handler.post()` will work in this situation even without a delay, because it will put the task of grabbing the `ViewHolder` at the end of the UI thread's queue, so the task will be called after the UI sets up the `RecyclerView` with its `ViewHolder`s and all. – AxiomaticNexus Mar 27 '22 at 21:58
  • @user25, you are not right. In many situations `postDelayed` helps, even if we are unsure that a timeout will be enough on old devices. Sometimes even 1 ms works best. Sometimes no other solutions found. We can set 1000 ms, if so unsure in several cases. – CoolMind Apr 06 '22 at 14:48
5

I know that is very late, but maybe is helpful for other people in the Kotlin era:

 binding.recycler.post {
     val view = binding.recycler.findViewHolderForAdapterPosition(position)?.itemView?.performClick()
 }
Alejandro Lagos
  • 179
  • 5
  • 19
1

you can do this:

postAndNotifyAdapter(new Handler(),mRecyclerView);



protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView) {
    handler.post(new Runnable() {
        @Override
        public void run() {
            if ( recyclerView.findViewHolderForLayoutPosition(0)!=null) {
                // This will call first item by calling "performClick()" of view.
                recyclerView.findViewHolderForLayoutPosition(0).itemView.performClick();
            } else {
                //   
                postAndNotifyAdapter(handler, recyclerView);
            }
        }
    });
}
Seachal
  • 27
  • 3
0

Hey I've solved the problem this way:

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

private TextView mTotalSum;
protected int mSum;
protected ArrayList<BasketRVAdapter.ViewHolder> mViewHolders;

public BasketRVAdapter(JSONArray recyclerItems, Context context, TextView totalSum) {
    super(recyclerItems, context);
    mTotalSum = totalSum;
    mViewHolders = new ArrayList<>();
}

@Override
public RecyclerView.ViewHolder onCreateSwipeViewHolder(ViewGroup parent, int i) {
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.view_basket_rv_item, parent, true);

    return new ViewHolder(view);
}

@Override
public void onBindSwipeViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
    final BasketRVAdapter.ViewHolder holder = (BasketRVAdapter.ViewHolder) viewHolder;

    JSONObject object;
    try {
        object = mRecyclerItems.getJSONObject(position);
        final int priceValue = object.getInt("price");
        final int quantityValue = object.getInt("quantity");

        holder.setId(object.getInt("id"));
        holder.title.setText(object.getString("title"));
        holder.price.setText(String.format(mContext.getString(R.string.price), priceValue));
        holder.quantity.setText(String.valueOf(quantityValue));
        holder.sum.setText(String.valueOf(priceValue * quantityValue));

        holder.quantity.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                String value = holder.quantity.getText().toString();

                if (value.equals("")) {
                    return;
                }

                if (value.equals("0")) {
                    Toast.makeText(mContext, R.string.null_value_error, Toast.LENGTH_SHORT).show();
                    return;
                }
                holder.sum.setText(String.valueOf(priceValue * Integer.valueOf(value)));
                setTotalSum();
            }

        });

        mViewHolders.add(holder);
        mSum += priceValue * quantityValue;

        if (position == getItemCount() - 1) {
            mTotalSum.setText(String.format(mContext.getString(R.string.total_sum), mSum));
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

@Override
public SwipeConfiguration onCreateSwipeConfiguration(Context context, int i) {
    return new SwipeConfiguration.Builder(context)
                                 .setLeftSwipeBehaviour(SwipeConfiguration.SwipeBehaviour.NORMAL_SWIPE)
                                 .setRightSwipeBehaviour(SwipeConfiguration.SwipeBehaviour.NO_SWIPE)
                                 .setLeftBackgroundColorResource(R.color.dt_basket_deleted)
                                 .setLeftUndoDescription(R.string.deleted)
                                 .setDescriptionTextColorResource(R.color.dt_white)
                                 .setLeftUndoable(true)
                                 .build();
}

@Override
public void onSwipe(int i, int i1, RecyclerView.ViewHolder holder) {
    try {
        if (i1 == SWIPE_LEFT) {
            remove(i);
            notifyItemRemoved(i);
            mViewHolders.remove(i);
            setTotalSum();
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

public int getSum() {
    return mSum;
}

public void setTotalSum() {
    int totalSum = 0;

    for (BasketRVAdapter.ViewHolder holder : mViewHolders) {
        totalSum += Integer.valueOf(holder.sum.getText().toString());
    }

    mSum = totalSum;
    mTotalSum.setText(String.format(mContext.getString(R.string.total_sum), totalSum));
}

public static class ViewHolder extends RecyclerView.ViewHolder {

    @Bind(R.id.trade_title)
    public TextView title;
    @Bind(R.id.trade_price)
    public TextView price;
    @Bind(R.id.trade_quantity)
    public EditText quantity;
    @Bind(R.id.sum)
    public TextView sum;

    private int mId;

    public ViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }

    public int getId() {
        return mId;
    }

    public void setId(int id) {
        mId = id;
    }
}}
  1. Added ArrayList mViewholders field inside class
  2. added each viewholder instance to mViewHolders arraylist inside onBindSwipeViewHolder method
  3. used mViewholders arraylist instead of findViewHolderForadapterposition method inside setTotalSum method
0

I wouldnt recommend using post delay. Although it works the timing is arbitrary.

Using OnPreDrawListener on the recyclerview, timing will be aligned with the next draw cycle and visible items are accessible.

sommer
  • 1