RecyclerView getAdapterPosition() returns -1 on a callback so I can't show the new appearance for the item
Asked Answered
I

2

8

Each item on my RecyclerView has a button that has three states: OPEN, LOADING, and CLOSED.

Initially all the buttons are in the OPEN state. When a button is clicked, the state is changed to LOADING and a network call is performed in the background. After the network call succeeds, the button state should be changed to CLOSED.

So in my adapter I used the following:

holder.button.setOnClickListener(v -> {
    holder.state = LOADING;
    notifyItemChanged(holder.getAdapterPosition()); /* 1 */
    callNetwork(..., () -> {
        /* this is the callback that runs on the main thread */
        holder.state = CLOSED;
        notifyItemChanged(holder.getAdapterPosition()); /* 2 */
    });
});

The LOADING state is always visualized correctly at /* 1 */ because getAdapterPosition() gives me the correct position.

However, the CLOSED state of the button is never visualized, because getAdapterPosition at /* 2 */ always returns -1.

I might understand getAdapterPosition() wrongly in this case.

How do I refresh the appearance of an item on a callback?

Immunochemistry answered 28/3, 2016 at 9:44 Comment(7)
just use getAdapterPosition(). why do you need holder there?Loup
@Loup getAdapterPosition is a method in ViewHolder. Did I miss something?Crawl
have your click listener in your viewholder. Implement onClickListener in your viewholder class and set a click listener there for your buttonLoup
@yuku I don't know yet why it returns -1 but can you try saving the result of /* 1 */ in a variable then use that variable instead of calling getAdapterPosition again?Yare
@gj_ it seems that the position could have changed when the network call is performed, since I can remove and add items on the fly.Crawl
@yuku check MidasLefko's answer, the callNetwork is a background process right? That means that the first call to notifyItemChanged will update your adapter which will result in -1 for the second call of notifyItemChangedYare
@yuku I haven't tried this but, can you save the holder itself instead of the position so you can modify the holder later on? I haven't completely understand how recyclerview recycles so I'm not sure it this is possibleYare
S
18

From the docs:

Note that if you've called notifyDataSetChanged(), until the next layout pass, the return value of this method will be NO_POSITION

NO_POSITION is a constant whose value is -1. This might explain why you are getting a return value of -1 here.

In any case, why don't you find the position of the model in the underlying dataset and then call notifyItemChanged(int position)? You could save the model as a field in the holder.

For example:

public class MyHolder extends RecyclerView.ViewHolder {
    
    private Model mMyModel;

    public MyHolder(Model myModel) {
        mMyModel = myModel;
    }
    
    public Model getMyModel() {
        return mMyModel;
    }
}

holder.button.setOnClickListener(v -> {
    holder.state = LOADING;
    notifyItemChanged(holder.getAdapterPosition());
    callNetwork(..., () -> {
        /* this is the callback that runs on the main thread */
        holder.state = CLOSED;
        int position = myList.indexOf(holder.getMyModel());
        notifyItemChanged(position);
    });
});

Alternatively you can just ignore if the position is -1, like this:

holder.button.setOnClickListener(v -> {
    holder.state = LOADING;
    int preNetworkCallPosition = holder.getAdapterPosition();
    if (preNetworkCallPosition != RecyclerView.NO_POSITION) {
        notifyItemChanged(preNetworkCallPosition);
    }
    callNetwork(..., () -> {
        /* this is the callback that runs on the main thread */
        holder.state = CLOSED;
        int postNetworkCallPosition = holder.getAdapterPosition();
        if (postNetworkCallPosition != RecyclerView.NO_POSITION) {
             notifyItemChanged(postNetworkCallPosition);
        }
    });
});
Stigma answered 28/3, 2016 at 9:55 Comment(1)
Yes the recyclerview animations sample by yigit boyar has int pos = getAdapterPosition(); if (pos != RecyclerView.NO_POSITION) {// do something and then notifyItemChanged(pos)}`. although not related to the topic watching this youtube.com/watch?v=imsr8NrIAMs might helpLoup
H
1

getAdapterPosition(); It will always return -1 when recyclerview makes layout calculations. You are calling this methods inside ViewHolder.. It means RecyclerView is doing calculations.

If you need position inside click actions of view, call it in the public void onClick(final View v) method for example:

"@Override public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {

    final Students user = mUsers.get(position);
    holder.Name.setText(user.getFullname());
    holder.Index.setText(user.getIndex_number());


    if (user.getThumbnail().equals("default")) {
        holder.profile_image.setImageResource(R.drawable.profile_pic);
    } else {
        Picasso.get().load(user.getThumbnail())
                .placeholder(R.drawable.profile_pic)
                .into(holder.profile_image);
    }

  holder.itemView.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(final View v) {
           **list_user_id = mUsers.get(position).getId();**

           Intent Sub = new Intent(mContext, UserProfileActivity.class);
           Sub.putExtra("user_id1", list_user_id);
           mContext.startActivity(Sub);

BUT NOT

getAdapterPosition(); It will always return -1 when recyclerview makes layout calculations. You are calling this methods inside ViewHolder.. It means RecyclerView is doing calculations.

If you need position inside click actions of view, call it in the public void onClick(final View v) method for example:

@Override public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {

    final Students user = mUsers.get(position);
    holder.Name.setText(user.getFullname());
    holder.Index.setText(user.getIndex_number());

**list_user_id = mUsers.get(position).getId();**



    if (user.getThumbnail().equals("default")) {
        holder.profile_image.setImageResource(R.drawable.profile_pic);
    } else {
        Picasso.get().load(user.getThumbnail())
                .placeholder(R.drawable.profile_pic)
                .into(holder.profile_image);
    }

  holder.itemView.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(final View v) {

           Intent Sub = new Intent(mContext, UserProfileActivity.class);
           Sub.putExtra("user_id1", list_user_id);
           mContext.startActivity(Sub);
Hyaluronidase answered 26/1, 2019 at 21:28 Comment(1)
This answer is simply wrong. It is true that getAdapterPosition() will return -1 while doing layout calculations, BUT that doesn't mean that methods inside ViewHolder imply "doing layout calculations" in any way, shape or form. And DO NOT use the final position from onBindVH on click listeners. It creates inconsistencies, please watch Yigit Boyar presentation on this topic.Calvo

© 2022 - 2024 — McMap. All rights reserved.