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
Post a Comment