Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView findViewHolder null for off-screen items

[first time asker and first time android coder so I apologize if I do something wrong]

I'm trying to change the value inside of an itemview on my recycler. However, if that itemview happens to be offscreen, the findViewBy[layout,adapter,or id] comes up null.

public void setUserActive(UserListAdapter adapter, RecyclerView recyclerView, int position, View v) {recyclerView.findViewHolderForItemId(adapter.getItemId(position));
    TextView txtActive = (TextView) vh.itemView.findViewById(R.id.user_isActive);
    txtActive.setText("Active");
    UserListAdapter.ViewHolder inactivevh = (UserListAdapter.ViewHolder) recyclerView.findViewHolderForItemId(adapter.getItemId(adapter.getActiveUserPosition()));
    TextView txtInActive = (TextView) inactivevh.itemView.findViewById(R.id.user_isActive);
    txtInActive.setText("");
    adapter.setActiveUserPosition(position);
}

I've tried a few different ways, from using

  UserListAdapter.ViewHolder vh = (UserListAdapter.ViewHolder) recyclerView.findViewHolderForLayoutPosition(position);

or

UserListAdapter.ViewHolder inactivevh = (UserListAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(position)

and they all fail when the view that should be inactivevh is off screen.

I've set the adapter to hasStableIDs = true notifydatasetchanged doesn't seem to help either.

like image 953
Brian P Grissom Avatar asked Sep 21 '15 16:09

Brian P Grissom


People also ask

What happens to a RecyclerView's ViewHolder when it scrolls off the screen?

viewholder does not maintain its state when scrolled off screen.

What does ViewHolder do in RecyclerView?

A ViewHolder describes an item view and metadata about its place within the RecyclerView. Adapter implementations should subclass ViewHolder and add fields for caching potentially expensive findViewById results.

How can we get ViewHolder from RecyclerView by position?

But there IS an easy way to get the ViewHolder from a specific position (something you'll probably do a lot in the Adapter). myRecyclerView. findViewHolderForAdapterPosition(pos); NOTE: If the View has been recycled, this will return null .

How do I get the selected item position in the adapter?

You can use the ViewHolder's getAdapterPosition() to retrieve the item's position within an interface method. Then store the clicked position in a member variable.


1 Answers

If an item is on screen (laid out) you can get its view holder conveniently using methods you described. If an item is not on screen it will get updated as soon as it gets scrolled in via onBindViewHolder. If it doesn't get scrolled in, it's not presented to user ergo any attempt to update it is a waste of processing power (apart from being impossible because the item doesn't have a view holder assigned).

Meaning: Implement your adapter in such way that onBindViewHolder is able to pull up-to-date data. Looks like you're all set up and can use this:

public void setUserActive(UserListAdapter adapter, RecyclerView recyclerView, int position) {
    // Retrieve the old active user ID so we can mark him deactivated.
    final int oldUserActivePosition = adapter.getActiveUserPosition();

    // Setup the adapter with current data.
    adapter.setActiveUserPosition(position);

    // Do NOT use stable IDs and this method. It just doesn't work as expected.
    // recyclerView.findViewHolderForItemId(adapter.getItemId(position));

    // Activate new user.
    UserListAdapter.ViewHolder holder = (UserListAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(position);
    if (holder == null) {
        // The item is off screen, no need to update it yet.
    } else {
        // Update the on screen item. The logic is already implemented in the adapter.
        adapter.onBindViewHolder(holder, position);
    }

    // Deactivate previous user.
    holder = (UserListAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(oldUserActivePosition);
    if (holder != null) {
        adapter.onBindViewHolder(holder, oldUserActivePosition);
    }
}

Simplify the if-else block in your code of course.

EDIT:

I just realized how unnecessarily complicated the original solution is. As long as the adapter can react to all events you need in onBindViewHolder and the binding is realtively easy this will suffice:

public void setActiveUserPosition(int position) {
    if (mActiveUserPosition != position) {
        int oldPosition = mActiveUserPosition;
        mActiveUserPosition = position;
        notifyItemChanged(oldPosition);
        notifyItemChanged(position);
    }
}

All you need to call now is adapter.setActiveUserPosition(position); which renders setUserActive method obsolete.

like image 122
Eugen Pechanec Avatar answered Oct 13 '22 15:10

Eugen Pechanec