android - Custom Sectioned RecyclerView Inconsistency detected if invoking notifyItemRemoved(position); -


i'm implementing sectionedrecyclerviewadapter. know there many great libraries task, before quit i'd enjoy try. please, before shoot @ me consider prototype , every suggestion improvements welcome.

here implementation. concept simple, every pojo wants sectioned must implement sectioned interface 1 method return title of section. linkedhashmap keep track of sections vs indices.

import android.support.v7.widget.recyclerview; import android.util.log; import android.view.viewgroup;  import java.util.arraylist; import java.util.collections; import java.util.comparator; import java.util.linkedhashmap; import java.util.list;   abstract public class sectionedrecyclerviewadapter<     headerviewholder extends recyclerview.viewholder,     valueviewholder extends recyclerview.viewholder>         extends             recyclerview.adapter<recyclerview.viewholder> {      public interface sectioned {         string getsection();     }      static final private string tag = "sectioned-rv";      private static final int type_normal     = 1;     private static final int type_header     = 0;      final     private list<sectioned>  mobjects;      final     private object   mlock;      final     private linkedhashmap<integer, string> sectionsindexer;      /* removed now, not sure if more elegant/better or worst     private final recyclerview.adapterdataobserver mdatasetobserver = new recyclerview.adapterdataobserver() {         @override         public void onchanged() {             calculatesectionheaders();         }          @override         public void onitemrangeremoved(int positionstart, int itemcount) {             onchanged();         }     };     */     private boolean         mnotifyonchange = true;       public sectionedrecyclerviewadapter() {         super();         mlock = new object();         sectionsindexer = new linkedhashmap<>();         mobjects = new arraylist<>();         sethasstableids(true);     }      public void changedata(list<? extends sectioned> data) {         synchronized (mlock) {             mobjects.clear();             if (data != null)                 mobjects.addall(data);             calculatesectionheaders();         }          if (mnotifyonchange) notifydatasetchanged();     }      public void removeitemat(int position) {         int viewtype = getitemviewtype(position);         if (viewtype == type_normal) {             int index = getsectionforposition(position);             synchronized (mlock) {                 mobjects.remove(index);                 calculatesectionheaders();             }             if (mnotifyonchange) notifyitemremoved(index); //<- crash             // if (mnotifyonchange) notifydatasetchanged(); //<- fine          }     }      public void clear() {         synchronized (mlock) {             mobjects.clear();             sectionsindexer.clear();         }          if (mnotifyonchange) notifydatasetchanged();     }      public void sort(comparator<? super sectioned> comparator) {         synchronized (mlock) {             collections.sort(mobjects, comparator);             calculatesectionheaders();         }          if (mnotifyonchange) notifydatasetchanged();     }      /**      * interceptor before sections calculated can transform computer data human readable,      * e.g. format unix timestamp, or status      *      * default method returns original data group.      * @param groupdata original group name      * @return transformed group name      */     protected string getcustomgroup(string groupdata) {         return groupdata;     }      @override     public int getitemcount() {         return mobjects.size() + sectionsindexer.size();     }      @override     public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) {         if (viewtype == type_header) {             return oncreateheaderviewholder(parent);         }          return oncreatevalueviewholder(parent);     }      @suppresswarnings("unchecked")     @override     public void onbindviewholder(recyclerview.viewholder holder, int position) {         int viewtype = getitemviewtype(position);         if (viewtype == type_normal) {             sectioned item = getitem(position);             if (item == null) {                 log.v(tag, "getitem(" + position + ") = null");                 return;             }              onbindvalueviewholder((valueviewholder)holder, position);         }          else {             final string group = sectionsindexer.get(position);             onbindheaderviewholder(group, (headerviewholder) holder);         }     }      public sectioned getitem(int position) {         if (getitemviewtype(position) == type_normal) {             return mobjects.get(getsectionforposition(position));         }         return mobjects.get(position);     }      @override     public long getitemid(int position) { return position; }      @override     public int getitemviewtype(int position) {         if (position == getpositionforsection(position)) {             return type_normal;         }         return type_header;     }      abstract     protected headerviewholder oncreateheaderviewholder(viewgroup parent);      abstract     protected void onbindheaderviewholder(string group, headerviewholder viewholder);      abstract     protected valueviewholder oncreatevalueviewholder(viewgroup parent);      abstract     protected void onbindvalueviewholder(valueviewholder holder, int position);      private int getpositionforsection(int section) {         if (sectionsindexer.containskey(section)) {             return section + 1;         }         return section;     }      private int getsectionforposition(int position) {         int offset = 0;         (integer key : sectionsindexer.keyset()) {             if (position > key) {                 offset++;             } else {                 break;             }         }          return position - offset;     }      private void calculatesectionheaders() {         int = 0;          string previous = "";         int count = 0;          sectionsindexer.clear();          (sectioned item: mobjects) {             final string group = getcustomgroup(item.getsection());             if (!previous.equals(group)) {                 sectionsindexer.put(i + count, group);                 previous = group;                  log.v(tag, "group <" + group + "> @ position: " + (i + count));                  count++;             }              i++;         }     } } 

everything works fine noticed when remove item calling removeitemat(position) if call notifyitemremoved(index); app crash exception:

java.lang.indexoutofboundsexception: inconsistency detected. invalid view holder adapter positionviewholder{855a1d3 position=3 id=4, oldpos=4, plpos:4 scrap [attachedscrap] tmpdetached no parent} @ android.support.v7.widget.recyclerview$recycler.validateviewholderforoffsetposition(recyclerview.java:5041)                                                     @ android.support.v7.widget.recyclerview$recycler.getviewforposition(recyclerview.java:5172) @ android.support.v7.widget.recyclerview$recycler.getviewforposition(recyclerview.java:5153) 

if call notifydatasetchanged(); after removing item, works fine.

so i'm curious why happens. debugging breakpoints recyclerview implementation noticed cause check on:

holder.mposition >= madapter.getitemcount() 

but cannot see why check fails if in implementation getitemcount return this:

@override public int getitemcount() {     return mobjects.size() + sectionsindexer.size(); } 

thank much


Comments

Popular posts from this blog

asynchronous - C# WinSCP .NET assembly: How to upload multiple files asynchronously -

aws api gateway - SerializationException in posting new Records via Dynamodb Proxy Service in API -

asp.net - Problems sending emails from forum -