2

Problem:

Kotlin by lazy throws unexpected NullPointerException when reached. Through debugger, when reached, this is properly evaluated and findViewById<ShapeableImageView>(R.id.image_view_icon) returns valid ImageView view, so I can't figure out why this happens - why is NullPointerException thrown if this is not null and if view exists and is already inflated at the point of lazy initialization?

Use case example:

I have custom Android view component - simple label view SimpleLabelView. This components supports setting icon and it's implementation defaults to setting it as a compound drawable to label TextView.. Now, I want to create a new component CompactIconLabelView which extends existing SimpleLabelView. New CompactIconLabelView uses different layout, and now I want to change the behavior of setting the icon - instead of setting it as a drawable to label, I want to set that drawable to custom ImageView that is contained in that new drawable. In order to do that, I need to initialize given ImageView lazily

Error

Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object kotlin.Lazy.getValue()' on a null object reference
at hr.simplifystay.sharedmodelslibrary.customViews.label.CompactIconLabelView.getIconImageViewThatCrashes(CompactIconLabelView.kt:40)
at hr.simplifystay.sharedmodelslibrary.customViews.label.CompactIconLabelView.setIcon(CompactIconLabelView.kt:37)
at hr.simplifystay.sharedmodelslibrary.customViews.label.SimpleLabelView.<init>(SimpleLabelView.kt:157)
at hr.simplifystay.sharedmodelslibrary.customViews.label.CompactIconLabelView.<init>(CompactIconLabelView.kt:24)
at hr.simplifystay.sharedmodelslibrary.customViews.label.CompactIconLabelView.<init>(CompactIconLabelView.kt:18)
at hr.simplifystay.sharedmodelslibrary.customViews.label.CompactIconLabelView.<init>(Unknown Source:14)

Code

SimpleLabelView

open class SimpleLabelView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int = R.attr.labelViewStyle,
    defStyle: Int = R.style.Theme_Simplify,
    @LayoutRes layoutResId: Int = R.layout.layout_simple_label
) : ConstraintLayout(context, attrs, defStyleAttr, defStyle) {

    @StyleableRes
    private val iconAttr = R.styleable.SimpleLabelView_icon

    open var icon: Drawable? = null
        set(value) {
            field = value
            label.setCompoundDrawablesRelativeWithIntrinsicBounds(value, null, null, null)
        }

    val label: MaterialTextView

    init {
        val view = inflate(context, layoutResId, this)
        ... some initializing code
        icon = typedArray.getDrawable(iconAttr) // Line 157 from stacktrace
        ... more code
    }
}

CompactIconLabelView

open class CompactIconLabelView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int = R.attr.labelViewStyle,
    defStyle: Int = R.style.Theme_Simplify,
    @LayoutRes layoutResId: Int = R.layout.layout_compact_icon_label
) : SimpleLabelView(context, attrs, defStyleAttr, defStyle, layoutResId) {


    override var icon: Drawable? = null
        get() = super.icon
        set(value) {
            field = value
            // This works
            if (!(::iconImageView.isInitialized))
                iconImageView = findViewById(R.id.image_view_icon)
            iconImageView.setImageDrawable(value)
            
            // This crashes
            iconImageViewThatCrashes.setImageDrawable(value) // Line 37 from stacktrace
        }

    private val iconImageViewThatCrashes: ShapeableImageView by lazy { findViewById(R.id.image_view_icon) } // Line 40 from stacktrace

    private lateinit var iconImageView: ShapeableImageView

}

So I am wondering, am I doing anything wrong or is this some kind of a bug in Kotlin?

2 Answers 2

0

The issue could arise from trying to access a lazily initialized property before its containing class is fully initialized.

What to Consider here : by lazy Lifecycle in Android Views: In Android, findViewById and view inflation rely on the View hierarchy being fully constructed and attached. But, by lazy runs its initializer the first time the property is accessed. If it's accessed too early, the Context or View hierarchy might not be ready.

Initialization Order in Kotlin: When a subclass is being initialized, the base class constructor is invoked first. If the setIcon method (or any other method accessing the lazy property) is called in the base class constructor, it can lead to the lazy property being accessed prematurely, which could result in NullPointerException.

About your question regarding that Why the Debugger Shows a Valid View so the answer may be, By the time you inspect the lazy property in the debugger, the View might have been fully initialized. But during actual runtime, the lazy initialization might be triggered too early. so to exactly check the same what you need to do is that you can check with the log. If the issue is that what i defined above, then your log will shows null value to you.

Sign up to request clarification or add additional context in comments.

1 Comment

Nothing being said actually explains this particular case, just regular Kotlin behavior.. Line findViewById(R.id.image_view_icon) is executed before crashed iconImageViewThatCrashes.setImageDrawable(value) and it successfully executes exact code that iconImageViewThatCrashes lazy init function executes.. That being said, it prooved that icon setter indeed has access to that view and that R.id.image_view_icon view exists in the hierarchy and it has been inflated
0

In Java, when creating a subclass object, the order of initialization is to first initialize the member variables of the parent class and call the constructor of the parent class, and then initialize the member variables of the subclass and call the constructor of the subclass.

Therefore, trying to access the member variables of the subclass within the constructor of the parent class will cause an error.

1 Comment

So you think that Java code is causing NullPointerException? In some way, that makes sense, but that does not explain why is lateinit var iconImageView initialized successfully - iconImageView = findViewById(R.id.image_view_icon)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.