RecyclerView.getChild(index) shows null when list is scrolled (index gets messed up)

19.8k views Asked by At

I've been using SwipeableRecyclerView for my android application for enabling swipes for my recyclerView. RecyclerView contains a list of cardViews.

I was trying to implement undo functionality for cards which will get deleted when swipe to left (first swipe shows undo, next swipe triggers delete)

I am trying the following code (partially working I guess)

SwipeableRecyclerViewTouchListener srvTouchListner = new SwipeableRecyclerViewTouchListener(rvTimerList,
            new SwipeableRecyclerViewTouchListener.SwipeListener(){

                @Override
                public boolean canSwipe(int i) {
                    return true;
                }

                @Override
                public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] ints) {
                    for(int position : ints){
                        View view = recyclerView.getChildAt(position);
                            if (view.getTag(R.string.card_undo) == null) {
                                if(viewStack == null) {
                                    saveToViewStack(position, view);
                                    final ViewGroup viewGroup = (ViewGroup) view.findViewById(R.id.time_card2);
                                    view.setTag(R.string.card_undo, "true");
                                    viewGroup.addView(view.inflate(TimerSummary.this, R.layout.timeslot_card_undo, null));
                                }
                            } else {
                                Log.d(TAG, "Removing Item");
                                deleteTimeSlot(timerInstanceList.get(position));
                                Toast.makeText(TimerSummary.this, "Deleted!", Toast.LENGTH_SHORT).show();
                                timerInstanceList.remove(position);
                                finalSummaryAdapter.notifyItemRemoved(position);
                            }

                    }
                    finalSummaryAdapter.notifyDataSetChanged();
                }
                @Override
                public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] ints) {
                    for (int position:ints){
                        View view = recyclerView.getChildAt(position);
                        if(view.getTag(R.string.card_undo) != null && view.getTag(R.string.card_undo).equals("true")){
                            viewStack = null;
                            recyclerView.setAdapter(finalSummaryAdapter);
                        }
                    }

                }
            });

when Items are more (needs scrolling)

View view = recyclerView.getChildAt(position);

returns a null reference which causes an app crash.

I doubt that I am using the wrong method for taking view. I should be using something related with viewholder, I am actually kind of confused about how to get the view which you want from viewholder.

If anybody can share anything that helps,, that'll be great! I ll be happy to provide any more infos if somebody wants it,

3

There are 3 answers

10
yigit On BEST ANSWER

You should use findViewHolderForAdapterPosition. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)

Keep in mind that it will return null if the position is not laid out (e.g. out of bounds or removed).

1
Shailendra Yadav On

Use findFirstCompletelyVisibleItemPosition() & findLastVisibleItemPosition() methods on LayoutManager of your RecyclerView to find out that the child you are looking for is visible or not. If it is visible then you can use getChild(index) to get the ViewHolder instance of that particular child and it wouldn't be null else it could be null(If the child is not in it). In case your child is not visible you just make changes in your specific position List object, next time whenever it'll be visible will have updated UI.

if (selectedPosition >= linearLayoutManager.findFirstVisibleItemPosition()
                    && selectedPosition <= linearLayoutManager.findLastVisibleItemPosition()){
                    linearLayoutManager.getChildAt(selectedPosition).setBackgroundResource(0);
                }
1
Brandon McAnsh On

Since I don't have enough reputation points to comment on @yigit's answer, I thought I would give a way to retrieve all viewHolders from a RecyclerView regardless of what is currently in the viewGroup. Even with findViewHolderForAdapterPosition and ForLayoutPosition we still can get a null instance of the ViewHolder in the RecyclerView.

To circumvent this, we can use findViewHolderForItemId[1] and pass the ID of the viewHolder in the adapter using RecyclerView.Adapter#getItemId(int position)[2]

for (int i = 0; i < mAdapter.getItemCount(); i++) {
    RecyclerView.ViewHolder holder = 
            mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
}
  1. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForItemId(int)
  2. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemId(int)