Reputation: 115
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?
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
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)
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?
Upvotes: 2
Views: 64
Reputation: 1
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.
Upvotes: 0
Reputation: 17
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.
Upvotes: 0