'Custom View Crashing Upon Inflation

My custom views are causing an app crash when they're inflated within their parent fragment.

I have a hunch that some update to either Kotlin or JDK may have contained a breaking change because these very same layouts used to work and inflate just fine.

What am I missing with rendering my custom views correctly?

Note I am using Kotlin synthetics.

Some things i've tried:

  • Using findViewById instead of Kotlin synthetics
  • Adding an attrs file (albeit the declare-styleable has no children since I don't use any custom attributes for this custom view)
  • Commenting out usage of rightArrow and leftArrow elements (this resolves the issue, but then my other custom views in the package throw the same inflation error)

The Error

The most base cause of the error taken from the full stacktrace below is: Attempt to invoke interface method 'java.lang.Object java.util.Map.get(java.lang.Object)' on a null object reference at com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog._$_findCachedViewById(GarmentIssuePickerDialog.kt)

Full stack trace:

     Caused by: android.view.InflateException: Binary XML file line #291: Binary XML file line #291: Error inflating class com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog
     Caused by: android.view.InflateException: Binary XML file line #291: Error inflating class com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
        at android.view.LayoutInflater.createView(LayoutInflater.java:645)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:787)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:858)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:518)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
        at com.nu.rms.inspections.ui.fragment.ContentFragment.onCreateView(ContentFragment.kt:98)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2699)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
        at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2181)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2004)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1959)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2641)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2589)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:247)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:541)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1248)
        at android.app.Activity.performStart(Activity.java:6696)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2628)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
        at android.os.Handler.dispatchMessage(Handler.java:102)
2022-05-04 12:53:31.557 4222-4222/com.nu.rms.inspections E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6119)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
     Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.Map.get(java.lang.Object)' on a null object reference
        at com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog._$_findCachedViewById(GarmentIssuePickerDialog.kt)
        at com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog.<init>(GarmentIssuePickerDialog.kt:50)
        at com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog.<init>(GarmentIssuePickerDialog.kt:25)
        at com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog.<init>(GarmentIssuePickerDialog.kt)
            ... 38 more

The Custom View Class File

package com.nu.rms.inspections.ui.widget

import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.content.Context
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.content.res.ResourcesCompat
import com.nu.rms.data.models.GarmentIssueAreas
import com.nu.rms.data.models.InspectionLocation
import com.nu.rms.data.models.Issue
import com.nu.rms.inspections.R
import com.urbn.nu.sharedcore.ext.animateShow
import com.urbn.nu.sharedcore.ext.crossFadeTo
import com.urbn.nu.sharedcore.ext.setActive
import com.urbn.nu.sharedcore.ext.show
import kotlinx.android.synthetic.main.view_issue_picker_dialog.view.*

class GarmentIssuePickerDialog @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    private lateinit var issue: Issue
    private var garmentIssueArea: GarmentIssueAreas? = null
    private var step: Steps = Steps.SELECT_ISSUE_AREA_BUTTON
    private lateinit var listener: GarmentIssuePickerInterface
    private var rightArrowEnlargeAnimator: ObjectAnimator
    private var leftArrowEnlargeAnimator: ObjectAnimator
    private var outsideIssueAreaDataChanged: Boolean = false
    private var insideIssueAreaDataChanged: Boolean = false

    interface GarmentIssuePickerInterface {
        fun setupGarmentIssuePickerFragments(
            numPages: Int,
            issue: Issue,
            issueArea: GarmentIssueAreas
        )
    }

    init {
        LayoutInflater.from(context).inflate(R.layout.view_issue_picker_dialog, this)
        rightArrowEnlargeAnimator = ObjectAnimator.ofPropertyValuesHolder(
            rightArrow, //<-------ERROR HAPPENS ON THIS LINE
            PropertyValuesHolder.ofFloat("scaleX", 1.3f),
            PropertyValuesHolder.ofFloat("scaleY", 1.3f)
        )

        leftArrowEnlargeAnimator = ObjectAnimator.ofPropertyValuesHolder(
            leftArrow,
            PropertyValuesHolder.ofFloat("scaleX", 1.3f),
            PropertyValuesHolder.ofFloat("scaleY", 1.3f)
        )

        rightArrowEnlargeAnimator.duration =
            resources.getInteger(R.integer.view_pager_dot_animation_duration).toLong()
        leftArrowEnlargeAnimator.duration =
            resources.getInteger(R.integer.view_pager_dot_animation_duration).toLong()
    }

    fun setListener(listener: GarmentIssuePickerInterface) {
        this.listener = listener
    }

...

        tintedOverlay.setOnClickListener {
            hideDialog()
        }
        closeButton.setOnClickListener {
            hideDialog()
        }
        modal.setOnClickListener { }

        rightArrow.setOnClickListener {
            if (issueAreasViewPager.currentItem != 1) {
                issueAreasViewPager.currentItem = 1
            }
        }

        leftArrow.setOnClickListener {
            if (issueAreasViewPager.currentItem != 0) {
                issueAreasViewPager.currentItem = 0
            }
        }
    }
}

The Custom View XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tintedOverlay"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black_80">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/modal"
        android:layout_width="1760dp"
        android:layout_height="920dp"
        android:layout_gravity="center"
        android:background="@color/white">

        <TextView
            android:id="@+id/issueName"
            style="@style/alert_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:gravity="center"
            android:maxLines="1"
            android:textSize="48sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="@string/issue_missing_damaged_embellishment" />

        <TextView
            android:id="@+id/issueDescription"
            style="@style/issue_description"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="@font/exo2_medium"
            android:textColor="@color/gray_subtext"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/issueName"
            tools:text="Spotting" />

        <ImageView
            android:id="@+id/closeButton"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_margin="24dp"
            android:src="@drawable/ic_close_light"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/continueButton"
            style="@style/alert_error_button_action"
            android:layout_width="417dp"
            android:layout_height="96dp"
            android:layout_marginBottom="40dp"
            android:background="@drawable/action_button_pass_bg"
            android:foreground="@drawable/ripple_press_dark"
            android:gravity="center"
            android:text="@string/action_continue"
            android:textAllCaps="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/step1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:layout_editor_absoluteX="0dp"
            tools:layout_editor_absoluteY="0dp">

            <TextView
                android:id="@+id/locationPrompt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="128dp"
                android:fontFamily="@font/exo2_regular"
                android:text="@string/where_is_the_issue_located"
                android:textAlignment="center"
                android:textColor="@color/black"
                android:textSize="40sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/outsideButton"
                style="@style/alert_error_button_action"
                android:layout_width="417dp"
                android:layout_height="96dp"
                android:layout_marginTop="40dp"
                android:background="@drawable/action_button_neutral_bg"
                android:foreground="@drawable/ripple_press_dark"
                android:gravity="center"
                android:text="@string/outside"
                android:textAllCaps="true"
                android:textColor="@color/black"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/locationPrompt" />

            <TextView
                android:id="@+id/insideButton"
                style="@style/alert_error_button_action"
                android:layout_width="417dp"
                android:layout_height="96dp"
                android:layout_marginTop="32dp"
                android:background="@drawable/action_button_neutral_bg"
                android:foreground="@drawable/ripple_press_dark"
                android:gravity="center"
                android:text="@string/inside"
                android:textAllCaps="true"
                android:textColor="@color/black"
                app:layout_constraintEnd_toEndOf="@id/outsideButton"
                app:layout_constraintStart_toStartOf="@id/outsideButton"
                app:layout_constraintTop_toBottomOf="@id/outsideButton" />

            <TextView
                android:id="@+id/outsideAndInsideButton"
                style="@style/alert_error_button_action"
                android:layout_width="417dp"
                android:layout_height="96dp"
                android:layout_marginTop="40dp"
                android:background="@drawable/action_button_neutral_bg"
                android:foreground="@drawable/ripple_press_dark"
                android:gravity="center"
                android:text="@string/outside_inside"
                android:textAllCaps="true"
                android:textColor="@color/black"
                app:layout_constraintEnd_toEndOf="@id/insideButton"
                app:layout_constraintStart_toStartOf="@id/insideButton"
                app:layout_constraintTop_toBottomOf="@id/insideButton" />

            <TextView
                android:id="@+id/entireGarmentButton"
                style="@style/alert_error_button_action"
                android:layout_width="417dp"
                android:layout_height="96dp"
                android:layout_marginTop="40dp"
                android:background="@drawable/action_button_neutral_bg"
                android:foreground="@drawable/ripple_press_dark"
                android:gravity="center"
                android:text="@string/entire_garment"
                android:textAllCaps="true"
                android:textColor="@color/black"
                app:layout_constraintEnd_toEndOf="@id/outsideAndInsideButton"
                app:layout_constraintStart_toStartOf="@id/outsideAndInsideButton"
                app:layout_constraintTop_toBottomOf="@id/outsideAndInsideButton" />
        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/step2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone">

            <TextView
                android:id="@+id/selectAreasPrompt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="128dp"
                android:fontFamily="@font/exo2_regular"
                android:text="@string/select_areas_on_the_garment"
                android:textColor="@color/black"
                android:textSize="40sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <!--TODO: comment these, must remove selectableItemBackgroundBorderless for unit tests-->
            <!--            <ImageView
                            android:id="@+id/leftArrow"
                            android:layout_width="60dp"
                            android:layout_height="60dp"
                            android:src="@drawable/ic_left_arrow"
                            android:visibility="gone"
                            app:layout_constraintBottom_toBottomOf="@id/issueAreasViewPager"
                            app:layout_constraintEnd_toStartOf="@id/issueAreasViewPager"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="@id/issueAreasViewPager" />

                        <ImageView
                            android:id="@+id/rightArrow"
                            android:layout_width="60dp"
                            android:layout_height="60dp"
                            android:src="@drawable/ic_right_arrow"
                            android:visibility="gone"
                            app:layout_constraintBottom_toBottomOf="@id/issueAreasViewPager"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintStart_toEndOf="@id/issueAreasViewPager"
                            app:layout_constraintTop_toTopOf="@id/issueAreasViewPager" />-->

            <!--TODO: uncomment these, the alternative is only used for unit testing-->
            <ImageView
                android:id="@+id/leftArrow"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:background="?selectableItemBackgroundBorderless"
                android:src="@drawable/ic_left_arrow"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="@id/issueAreasViewPager"
                app:layout_constraintEnd_toStartOf="@id/issueAreasViewPager"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@id/issueAreasViewPager" />

            <ImageView
                android:id="@+id/rightArrow"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:background="?selectableItemBackgroundBorderless"
                android:src="@drawable/ic_right_arrow"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="@id/issueAreasViewPager"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/issueAreasViewPager"
                app:layout_constraintTop_toTopOf="@id/issueAreasViewPager" />

            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/issueAreasViewPager"
                android:layout_width="1061dp"
                android:layout_height="585dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/selectAreasPrompt" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

The Custom View being Referenced in Parent Fragment Layout

    <com.nu.rms.inspections.ui.widget.GarmentIssuePickerDialog
        android:id="@+id/garmentIssuePickerDialog"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source