RecyclerView.Adapter onBindViewHolder() gets wrong position
Asked Answered
B

4

13

I'll show the code and after the steps to get the problem.

I have a recyclerview inside a tabbed fragment that takes the dataset from a custom object:

mRecyclerView = (RecyclerView) v.findViewById(R.id.recyclerview);

mRecyclerView.setLayoutManager(mLayoutManager);

mRecyclerAdapter = new MyRecyclerAdapter(mMes.getListaItens(), this, getActivity());

mRecyclerView.setAdapter(mRecyclerAdapter);

I set the longclick behavior of the list items in onBindViewHolder() of the adapter:

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

    ItemMes item = mListaItens.get((position));

    holder.descricao.setText(item.getDescrição());
    holder.valor.setText(MainActivity.decimalFormatWithCod.format(item.getValor()));

    ...

    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {

            new MaterialDialog.Builder(mContext)
                    .title(holder.descricao.getText().toString())
                    .items(R.array.opcoes_longclick_item)
                    .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() {
                        @Override
                        public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {

                            switch (which) {
                                case 0:
                                    mParentFragment.showUpdateItemDialog(position);
                                    return true;

                                case 1:
                                    mParentFragment.showDeleteItemDialog(position);
                                    return true;
                            }

                            return false;
                        }
                    })
                    .show();

            return true;
        }
    });

}

Then, the methods in the fragment that take care of delete the item itself:

public void showDeleteItemDialog(int position) {

    final ItemMes item = mMes.getListaItens().get(position);

    new MaterialDialog.Builder(getActivity())
            .title("Confirmar Remoção")
            .content("Tem certeza que deseja remover " + item.getDescrição() + "?")
            .positiveText("Sim")
            .negativeText("Cancelar")
            .onPositive(new MaterialDialog.SingleButtonCallback() {
                @Override
                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                    deleteItem(item);
                }
            })
            .show();

}

public void deleteItem(ItemMes item) {

    getMainActivity().deleteItemFromDatabase(item.getID());

    int position = mMes.getListaItens().indexOf(item);

    mMes.getListaItens().remove(position);

    mRecyclerAdapter.notifyItemRemoved(position);

    atualizaFragment();

}

And finally the method in activity that do the DB operation:

 public int deleteItemFromDatabase(long id) {

    SQLiteDatabase db = dataBaseHelper.getWritableDatabase();

    String where = DBHelper.COLUNA_ID + " = ?";

    String[] args = {String.valueOf(id)};

    int rowsAffected = db.delete(DBHelper.TABELA_ITEM, where, args);

    db.close();

    return rowsAffected;

}

Now i'll reproduce the steps: I'm showing 3 itens in the listview. Then I try to remove the first:

1 - The longclick is intercepted passing the correct index: enter image description here

2 - The item is correctly deleted from the database: enter image description here

3 - After all this, as expected, the adapter is storing and showing 2 items... enter image description here

SO, if I try to delete the first item of this 2 item list I get the wrong position (should be 0, is 1): The position = 1

And also if I try to delete the last item of this 2 item list I get the wrong position (should be 1, is 2): enter image description here

The question is: If I have a dataset of size 2 (and the adapter knows it), how can it call onBindViewHolder(ViewHolder holder, int [last index +1])? enter image description here

I have no idea what could be wrong. So I ask help cause I'm thinking about give up this project cause I do everything right but always something dont works, and Im tired. Thanks in advance.

Bicameral answered 2/5, 2016 at 15:26 Comment(5)
You'll need to include your adapter code as well. This is most likely happening because your adapter isn't handling the 'delete' correctly. It would need to reduce its total number. If you're using a simple list where the IDs are the position, you'll run into a problem where your views will need to be re-created as well.Unselfish
Without seeing the complete code, it's difficult to say for sure.Unselfish
Tell which part you want and I post, if I put everything will be bad. Maybe I could paste some file url on the bitbucket...Bicameral
The adapter and view holder code would be the most critical thing here. If it is too big/complicated then I suggest creating a smaller project (or even different view) that does the minimum of what you need to demonstrate the problem. That will likely be easier for those trying to help and may even give you an easier look at what is going on.Unselfish
pastebin.com/E44CcM7UBicameral
B
17

I've noticed that in method onBindViewHolder(VH holder, int position) while the position was comming wrong, the holder.getAdapterPosition() gives me always the correct position.

So I changed my code from:

ItemMes item = mListaItens.get((position));

...

mParentFragment.showUpdateItemDialog(position);

...

mParentFragment.showDeleteItemDialog(position);

....

To:

 ItemMes item = mListaItens.get((holder.getAdapterPosition()));

...

mParentFragment.showUpdateItemDialog(holder.getAdapterPosition());

...

mParentFragment.showDeleteItemDialog(holder.getAdapterPosition());

....

And everything works well. This is very strange but... Thanks everybody.

Bicameral answered 2/5, 2016 at 19:31 Comment(0)
U
4

Took a look at the adapter code you provided in the comment and it's pretty straightforward. Try this: rather than call notifyItemRemoved(), call notifyDataSetChanged(). This is rather expensive as it will cause your adapter to re-bind the data set (and re-create ViewHolders), but since you're using an ArrayList where you are removing an element, it's really the simplest way to do it. Otherwise you'll have to track the position of the items and when an item is removed it cannot change the position of other items - or handle the case where items shift their position in the data set.

Unselfish answered 2/5, 2016 at 18:50 Comment(1)
I may have a similar problem here: #43532400. I would appreciate any thoughts or ideas on how to fix.Drear
H
3

According to RecyclerView's getAdapterPosition documentation:

RecyclerView does not handle any adapter updates until the next layout traversal. This may create temporary inconsistencies between what user sees on the screen and what adapter contents have. This inconsistency is not important since it will be less than 16ms but it might be a problem if you want to use ViewHolder position to access the adapter. Sometimes, you may need to get the exact adapter position to do some actions in response to user events. In that case, you should use this method which will calculate the Adapter position of the ViewHolder.

So in case of implementing user events, using getAdapterPosition is a recommended way to go.

Hennery answered 8/4, 2019 at 5:58 Comment(0)
P
2

Try this code in onBindViewHolder()

int adapterPos=holder.getAdapterPosition();
        if (adapterPos<0){
            adapterPos*=-1;
        }

ItemMes item = mListaItens.get((adapterPos));
mParentFragment.showUpdateItemDialog(adapterPos);

Use adapterPos instead of position variable.

Pharmaceutics answered 21/2, 2018 at 2:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.