12

Why exception execute when I removed some items in RecyclerView by using loop ? I used Collentions.synchronizedMap in adapter and 'deleteItem method' use synchronized too (the method in fragment).

public void elementController(JsonObject jsonObject , String type)   {
    if ( jsonObject == null || type == null )    {
        return;
    }

    int position =0 , resultPosition =0;
    if ( type.equals("update") || type.equals("delete"))    {

        String id = jsonObject.get(ELEMENT_ID).getAsString();
        Map<String , Element> map = gridFragment.getMap();

        synchronized (map) {  
            for (String s : map.keySet()) {
                if (s.equals(id)) {
                    resultPosition = position;
                } else {
                    position++;
                }
            }
        }
    }

    if(position-1 > gridFragment.getmAdapter().getData().size() || position <0)   {
        return;
    }
    switch (type)   {
        case "add":
            if (gridFragment.addElement(MyJsonParser.ElementParse(jsonObject),0)){
                LogUtils.logDebug(TAG,"add end");
            }
            break;
        case "update":
            if(gridFragment.updateElement( updateParser(jsonObject),resultPosition)){
                LogUtils.logDebug(TAG,"update end");
            }
            break;
        case "delete":
            if(gridFragment.deleteElement(jsonObject.get(ELEMENT_ID).getAsString(),resultPosition)){
                LogUtils.logDebug(TAG,"delete end");
            }
            break;
    }
}

public boolean deleteElement(final String id , final int position){
    new Thread(new Runnable() {
        @Override
        public void run() {
            getActivity().runOnUiThread(new Runnable(){
                @Override
                public void run() {
                    synchronized (map) {
                        map.remove(id);
                        mAdapter.setData(map);
                        mAdapter.notifyItemRemoved(position);
                    }
                }
            });
        }
    }).start();

    return true;
} 

My error Log:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:0).state:4
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
        at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:356)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
        at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
        at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:151)
        at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
        at android.support.v7.widget.RecyclerView.resumeRequestLayout(RecyclerView.java:1171)
        at android.support.v7.widget.RecyclerView$1.run(RecyclerView.java:167)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
        at android.view.Choreographer.doCallbacks(Choreographer.java:574)
        at android.view.Choreographer.doFrame(Choreographer.java:543)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:212)
        at android.app.ActivityThread.main(ActivityThread.java:5137)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:718)
        at dalvik.system.NativeStart.main(Native Method)

device not found

Tom11
  • 2,419
  • 8
  • 30
  • 56
jihoon kim
  • 1,270
  • 1
  • 16
  • 22
  • 1
    first code doesnt make sense, you do loop and only get one position from loop. no break condition – Jemshit Apr 10 '15 at 16:12
  • You're setting your mAdapter data after removing the item, and then attempting to remove the specified position, which in this case does not exist. – Alex Hart Apr 10 '15 at 16:16
  • all of first code called by service (i use socket network with server). i got jsonarray from server and send jsonobjects to first code in jsonarray by for loop. so it is first loop. i guess second jsonobject was sended by params to first code before ending first code. – jihoon kim Apr 10 '15 at 16:40
  • I add all of my first method. – jihoon kim Apr 10 '15 at 16:43

6 Answers6

17

For animation does not disappear, use:

holder.deleteItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                itemList.remove(position);
                notifyItemRemoved(position);
                notifyItemRangeChanged(position, itemList.size());
            }
        });
Nastromo
  • 622
  • 1
  • 9
  • 16
9

Haven't read your code because I'm not familiar with the classes that you use but I know a fix to this problem. The problem occurs when the row element deleted is not the last one because of the index discrepancy between dataList index (your ArrayList to store data) and the parameter final int position in the method onBindViewHolder. Let me explain the issue in a graphical manner:

enter image description here

suppose we have a RecyclerView with three rows and we delete the 3rd row, at dataList index 2, (note that the deleted one is not the last element) after the removal, dataList index for the last element reduces 3 from 2 however, final int position still retrieves its position as 3 and that causes the problem. So, after removal, you can't use final int position as an index of element to be removed from dataList.

To fix this, introduce int shift=0 and in a while loop, try removing the third item at (position-shift) and if it fails (in my case, it will) increment the shift and try removing it again until no exception occurs.

 public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
    
    ...
    
        holder.removeButton.setOnClickListener(new View.OnClickListener(){ //button used to remove rows
                    @Override
                    public void onClick(View view) {
                        if (position == dataList.size() - 1) { // if last element is deleted, no need to shift
                            dataList.remove(position);
                            notifyItemRemoved(position);
                        } else { // if the element deleted is not the last one
                            int shift=1; // not zero, shift=0 is the case where position == dataList.size() - 1, which is already checked above
                            while (true) {
                                try {
                                    dataList.remove(position-shift);
                                    notifyItemRemoved(position);
                                    break;
                                } catch (IndexOutOfBoundsException e) { // if fails, increment the shift and try again
                                    shift++;
                                }
                            }
                        }
                    }
                });
   ...
    }
fdermishin
  • 3,519
  • 3
  • 24
  • 45
Ozan
  • 1,381
  • 2
  • 10
  • 13
2

Yes, that happens because of concurrency access. I've encountered it. Easy to fix with Iterator or with a nice trick: you start to remove from the last item. However Z-A order with Iterator is better.

Recently I've improved and created a FlexibleAdapter which uses this solution. Please, also have a look at the description and full working example: https://github.com/davideas/FlexibleAdapter

Davideas
  • 3,226
  • 2
  • 33
  • 51
1

it is works for me,like this:

fun removeData(position: Int) {
    this.notifyItemRemoved(position)
    this.notifyItemRangeChanged(position, imgList.size)
    this.imgList.removeAt(position)
}
tao tang
  • 11
  • 1
0

Actually, It's simpler than you think, just move map.notifyItemRemoved(position) from this

...
public void run() {
                    synchronized (map) {
                        map.remove(id);
                        mAdapter.setData(map);
                        mAdapter.notifyItemRemoved(position);
                    }
                }

to this

public void run() {
                    synchronized (map) {
                        mAdapter.notifyItemRemoved(position);
                        map.remove(id);
                        mAdapter.setData(map);
                    }
                }
user1767316
  • 3,276
  • 3
  • 37
  • 46
-1
 mAdapter.notifyItemRemoved(position); 

this only notifies the observers that are listening for layout changes.

Instead, try calling:

mAdpater.nofityDataChanged()
Tom11
  • 2,419
  • 8
  • 30
  • 56
LostPuppy
  • 2,471
  • 4
  • 23
  • 34
  • 8
    Don't call nofityDataChanged() unless you change the whole list content otherwise all animations are killed because Item is rebinded. Please have a look at my answer and at my FlexibleAdapter for all *notify()* methods – Davideas Jul 20 '15 at 20:28