Verified
Status Update
Comments
yb...@google.com <yb...@google.com>
cc...@google.com <cc...@google.com> #2
Thanks for the thorough report - you're right about the problem, and the workaround looks good - it even handles the fast cases for initialization and setList(null) nicely.
Fix and tests submitted internally, should go out with next paging release.
Fix and tests submitted internally, should go out with next paging release.
cc...@google.com <cc...@google.com> #3
Actually, I'd like to know more about what failed here and why.
I'm curious if InstantTaskExecutorRule is putting paging into an unexpected state - do you have a sample project that reproduces the issue?
When hitting this outside of a test, are you specifying any custom executors, possibly ones which run main thread tasks immediately instead of posting them?
I'm curious if InstantTaskExecutorRule is putting paging into an unexpected state - do you have a sample project that reproduces the issue?
When hitting this outside of a test, are you specifying any custom executors, possibly ones which run main thread tasks immediately instead of posting them?
56...@gmail.com <56...@gmail.com> #4
I don't have a sample project that hits this.
The problem does not happen very often outside of the test scripts, but we are not running any custom executors. The PagedListAdapter gets the data through a LivePagedListProvider from room db.
The problem does not happen very often outside of the test scripts, but we are not running any custom executors. The PagedListAdapter gets the data through a LivePagedListProvider from room db.
cc...@google.com <cc...@google.com> #5
This specific issue should be fixed with the Paging Alpha 4 that just released.
Filedhttps://issuetracker.google.com/issues/70351983 to track testing around InstantTaskExecutorRule.
Filed
Description
Version used: 1.0.0-alpha2
Devices/Android versions reproduced on:
Android O
Unit tests in Android studio
- Sample project to trigger the issue.
- A screenrecord or screenshots showing the issue (if UI related).
We are seeing crashes with the following stack trace:
10-03 09:42:42.633 26917 26917 E AndroidRuntime: FATAL EXCEPTION: main
10-03 09:42:42.633 26917 26917 E AndroidRuntime: Process:
10-03 09:42:42.633 26917 26917 E AndroidRuntime: java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{d08cbcb position=84 id=-1, oldPos=75, pLpos:55 scrap [attachedScrap] tmpDetached no parent} android.support.v7.widget.RecyclerView{804e943 VFED..... ......ID 0,0-1080,795 #7f080071 app:id/group_feed_recycler_view}, adapter:com.app.name.ui.main.GroupFeedRowAdapter@5fbebc0, layout:android.support.v7.widget.LinearLayoutManager@49810f9, context:com.app.name.ui.main.MainActivity@cf99efb
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5421)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5603)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5563)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5559)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2229)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1556)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1516)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:618)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3644)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3408)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1710)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:4806)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:723)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:655)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:789)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:98)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.os.Looper.loop(Looper.java:164)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6541)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
10-03 09:42:42.633 26917 26917 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
We also see the same issue when running unit tests using the instant run rule:
@Rule
public TestRule instantRunRule = new InstantTaskExecutorRule();
We think the problem is that the adapter is calling notifyItemRangeInserted before updating the list the adapter is using.
We are currently using a workaround in our code to try and provide the correct list when the adapter is asked for it:
private List<Item> mListSnapshot;
private PagedList<Item> mList;
public void setList(PagedList<Item> list)
{
mList = list;
mListSnapshot = list.snapshot();
super.setList(list);
}
public int getItemCount()
{
if (mList != super.getCurrentList())
{
// The adapter is calculating a diff
return (mListSnapshot == null) ? 0 : mListSnapshot.size();
}
return (mList == null) ? 0 : mList.size();
}
public Item getItem(int index)
{
if (mList != super.getCurrentList())
{
if (mListSnapshot == null)
{
throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
}
// No loadAround as the snapshot is not a paged list
return mListSnapshot.get(index);
}
else
{
if (mList == null)
{
throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
}
mList.loadAround(index);
return mList.get(index);
}
}
This code means that the values from the snapshot of the list are returned when notifyItemRangeInserted is called after calculating the diff (rather than the old list). The snapshot is used as that is what the diff was calculated against.
Can you tell me if there is a specific reason the default adapter calls notifyItemRangeInserted before updating the list, and if there are any big problems that may be encountered using the above workaround?