Search code examples
androidkotlinnullpointerexceptionkotlin-delegate

NullPointerException (NPE) when using Kotlin property delegate with by


I have a class that takes user inputs in a text field and converts them to an any class using the supplied functions

class GenericTextFieldDelegate<T>(
    private val initBlock: () -> TextView,
    private val getConversion: (String?) -> T?,
    private val setConversion: (T?) -> String? = { it?.toString() }
    ) {
    private val textView: TextView by lazy { initBlock() }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
        getConversion(textView.text?.toString())

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        textView.text = setConversion(value)
    }
}

I have done this so that when I have TextViews I can do this

class IntegerInputView @JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attributeSet, defStyleAttr), DataInput<Int> {

override var value: Int? by GenericTextFieldDelegate(
    { inputET },
    getConversion = { it?.toIntOrNull() },
    setConversion = { it.toString() }
)
...

I have a fragment that has an above custom view and when I have

override var tareWeight: Kg?
    get() = tareWeightInput.value
    set(value) {
        tareWeightInput.value = value
    }

all works fine, by what I really want to do is

override var tareWeight: Kg? by tareWeightInput

adding these lines to IntegerInputView

...

operator fun getValue(thisRef: Any?, property: KProperty<*>): Int? = value

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int?) {
    this.value = value
}

override var value: Int? by GenericTextFieldDelegate(
...

When I build, run and load the fragment this I get the below stack trace. Where am I going wrong?

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Integer com.gameforeverything.storekeeper.customViews.IntegerInputView.getValue(java.lang.Object, kotlin.reflect.KProperty)' on a null object reference
        at com.gameforeverything.storekeeper.fragments.weighInFragment.WeighInFragment.getGrossWeight(Unknown Source:7)
        at com.gameforeverything.storekeeper.fragments.weighInFragment.WeighInPresenter.getNetWeight(WeighInPresenter.kt:40)

Solution

  • Your property delegate itself (tareWeightInput in this example) is null.

    You can tell that this is the case by examining the stack trace: it notes that the method call that failed due to the NPE was IntegerInputView.getValue. Since this is the property delegate method call being called on the delegate in order to retrieve the value of the property, we know that the delegate must be null.

    Your property delegate is a View, so I suspect it's being retrieved dynamically somehow, possibly to the result of a findViewById call. You need to ensure that the variable containing the delegate is non-null at the moment it's retrieved. Consider this similar example:

    import kotlin.reflect.KProperty
    
    class FooDelegate {
        private var value: Int = 0
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { this.value = value }
    }
    
    class Foo {
        private lateinit var delegate: FooDelegate
        val bar by delegate
        
        init {
            delegate = FooDelegate()
        }
    }
    
    fun main() {
        println(Foo().bar)
    }
    

    This crashes, because it tries to set up the delegate before the variable is initialized. In this case, Kotlin throws the error earlier, but this may be being masked in your example by platform types due to Kotlin/Java interop.