1

I am using the RecyclerView from androidx (see here).

I am finding that when a row of my RecyclerView has scrolled off screen, recyclerView.findViewHolderForAdapterPosition() for the associated position returns null (as expected).

But what is not expected is that the associated row hasn't yet been recycled. I am checking this by overriding RecyclerView.Adapter.onViewRecycled() and seeing what is recycled.

So why is a null ViewHolder returned for a row that hasn't been recycled?

And a related question is: how do I find those rows that haven't yet been recycled (apart from keeping a list myself using onViewRecycled())?

The relevant code I'm using is as follows:

In my Activity:

// find the row (ViewHolder) for row with specified 'tag'
int position = customAdapter.getPositionForTag(tag);
ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
// viewHolder above is null even if the associated row (with same 'tag') hasn't been recycled yet

In my CustomAdapter, which extends RecyclerView.Adapter:

int getPositionForTag(String tag) {
    for (int i = 0; i < dataSet.size(); i++) {
        if (dataSet.get(i).getTag().equals(tag)) {
            return i;
        }
    }
    return -1;
}

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    // here I monitor which rows are recycled
    // I add the 'tag' to the ViewHolder as a convenience, when binding ViewHolder (see below)
    Log.d(TAG, "onViewRecycled for tag: " + holder.tag);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    // add tag to holder as a convenience...
    holder.tag = dataSet.get(position).getTag();
    // ... now setup and add views to holder
}
drmrbrewer
  • 11,491
  • 21
  • 85
  • 181
  • 2
    Even though viewholder was not recycled it was probably already detached - see [`onViewDetachedFromWindow(VH ViewHolder)`](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onViewDetachedFromWindow(VH)). – Pawel May 14 '21 at 10:46
  • @Pawel yes it looks like you are right. Detached but not yet recycled. So how about the related question I asked (slightly rephrased): how do I find a row (for a particular `tag` in my case) that hasn't yet been detached (or recycled)? Feel free to add an answer (even for the first part of the question you already answered) and I can mark it correct. Thanks. – drmrbrewer May 14 '21 at 10:55
  • @Pawel The problem I'm trying to solve in my case is that the rows that are detached (but not yet recycled) come back into view, they seem to bring their previous state (particularly the state of UI controls like checkboxes etc)... I need to call a function which changes the state of all UI controls that are still (or which may still become) "active"... but even a `findViewById()` on the top-level container view doesn't find these "detached but not recycled" views. – drmrbrewer May 14 '21 at 11:13
  • 2
    If your dataset changes and you need to update state of a viewholder that's already "laid out" you should dispatch an [update payload](https://stackoverflow.com/q/33176336/9241978) to your adapter which will safely deliver it to `onBindViewHolder (VH holder, int position, List payloads)` or discard it (if viewholder for `position` is not bound). Manipulating viewholder directly by crawling through view hierarchy is not recommended. – Pawel May 14 '21 at 12:42
  • That seems like sound advice, thanks. – drmrbrewer May 14 '21 at 15:27
  • @Pawel I'm using `notifyItemRangeChanged()` (because every row is potentially affected and might need updating)... but I'm finding that `onBindViewHolder()` is called for each active `ViewHolder`, and **after that** `onViewDetachedFromWindow()` and then `onViewRecycled()`... i.e. the rows I'm trying to update just become detached and recycled :-/ I'm guessing this shouldn't happen. – drmrbrewer May 16 '21 at 09:46
  • 1
    change without update payload is considered "item replaced" so it's expected as new viewholder is put in that position. If you don't know what items are to be updated consider using [`DiffUtil`](https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil) – Pawel May 16 '21 at 10:47
  • Ah, interesting. So I need to be calling `notifyItemRangeChanged (int positionStart, int itemCount, Object payload)`... even if I ignore the `payload` (for now, until I move towards a better overall structure in which a sensible `payload` is passed)? – drmrbrewer May 16 '21 at 11:00

0 Answers0