gotcha

gotcha

Recorded RecyclerView.shouldIgnore() NPE

Background#

This week, while working on a commercial requirement (advertising), I made some simple changes to the passed-in Bean for easy reuse. The first run went smoothly, but when I ran it again for the second time, I encountered a NullPointerException with the RecyclerView, and the exception stack trace showed the following information (I didn't save the log at that time, but found the same exception stack trace from someone else, and the reason was the same):

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean androidx.recyclerview.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:3951)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3652)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.appcompat.widget.LinearLayoutCompat.setChildFrame(LinearLayoutCompat.java:1645)
at androidx.appcompat.widget.LinearLayoutCompat.layoutVertical(LinearLayoutCompat.java:1499)
at androidx.appcompat.widget.LinearLayoutCompat.onLayout(LinearLayoutCompat.java:1407)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.layoutDecoratedWithMargins(RecyclerView.java:9322)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1615)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3641)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:758)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2484)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2200)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1386)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6733)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:723)
2018-10-20 19:24:30.408 20730-20730/com.wynk.basic E/AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:658)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

I initially thought it was an issue with Android Studio (which is common on my work computer), so I tried clearing the cache, restarting, and reinstalling, but the problem persisted. Finally, I realized that the issue was with the code itself.
The scope of the changes I made was very small, making it easy to identify. The exception mentioned RecyclerView, which hadn't occurred before, so I suspected that there was an issue with the code. I compared the code with the previous version and couldn't find any significant differences, which left me confused and questioning my life choices...

Identifying the Issue#

I couldn't figure it out, but I found the code that threw the exception: ViewHolder.shouldIgnore():

// Only the key lines of code are retained
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i);
    if (holder.shouldIgnore()) {
        continue;
    }
    
// getChildViewHolderInt
    static ViewHolder getChildViewHolderInt(View child) {
        if (child == null) {
            return null;
        }
        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
    }

Actually, at this point, the issue was almost clear. This situation would only occur if child is null or if mViewHolder in LayoutParams is null. Since there was no code to remove the child View, it could only be that mViewHolder was null, but... I couldn't see it.

When I searched for the exception message RecyclerView$ViewHolder.shouldIgnore() NullPointerException on Google, I found this and looked at this image:

I went back to the project and found the code, and sure enough, in order to recalculate the width and height of the View, the LayoutParams of ViewHolder#mItemView were overwritten with a new object, similar to this:

mBinding.getRoot().setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

This definitely caused mViewHolder in LayoutParams to become null.

Solution#

The reason was that the original LayoutParams were replaced, and the new LayoutParams didn't assign a value to mViewHolder. By using mAdBinding.getRoot().getLayoutParams() and modifying the properties, the issue was resolved. Like this:

ViewGroup.LayoutParams layoutParams = mBinding.getRoot().getLayoutParams();
                    layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                    layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.