I'm trying to update a ViewHolder
in a RecyclerView
without rebinding the entire thing. According to the docs, I should do this by calling RecyclerView.Adapter.notifyItemChanged(int position, Object payload)
, where payload
is an arbitrary object that will be passed to RecyclerView.Adapter.onBindViewHolder(VH holder, int position, List<Object> payloads)
, where i'll be able to update the ViewHolder.
But when I try this, onBindViewHolder
always receives an empty list. Between these two calls, the internal list of payloads is cleared. After setting breakpoints at the source code of RecyclerView, this happens because of a relayout, which eventually calls RecyclerView.ViewHolder.clearPayload()
Has anyone else managed to get this working? Is this a bug in the support library or is something I've done triggering a relayout between these two functions?
Here's the stack trace for when the payload is cleared:
"<1> main@831692616832" prio=5 runnable
java.lang.Thread.State: RUNNABLE
at android.support.v7.widget.RecyclerView$ViewHolder.clearPayload(RecyclerView.java:8524)
at android.support.v7.widget.RecyclerView$ViewHolder.resetInternal(RecyclerView.java:8553)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4544)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4461)
at android.support.v7.widget.LayoutState.next(LayoutState.java:86)
at android.support.v7.widget.StaggeredGridLayoutManager.fill(StaggeredGridLayoutManager.java:1423)
at android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.java:610)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2847)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3145)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:581)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.support.v4.widget.DrawerLayout.onLayout(DrawerLayout.java:1043)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:14289)
at android.view.ViewGroup.layout(ViewGroup.java:4562)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1976)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1730)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
at android.view.Choreographer.doCallbacks(Choreographer.java:562)
at android.view.Choreographer.doFrame(Choreographer.java:532)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Method.java:-1)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(NativeStart.java:-1)
RecyclerView
by default creates another copy of the ViewHolder
in order to fade the views into each other. This causes the problem because the old ViewHolder
gets the payload but then the new one doesn't. So you need to explicitly tell it to reuse the old one:
DefaultItemAnimator animator = new DefaultItemAnimator() {
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
};
mRecyclerView.setItemAnimator(animator);