In my activity I have a field that should be non-nullable and has a custom setter. I want to initialize the field in my onCreate
method so I added lateinit
to my variable declaration. But, apparently you cannot do that (at the moment): https://discuss.kotlinlang.org/t/lateinit-modifier-is-not-allowed-on-custom-setter/1999.
These are the workarounds I can see:
- Do it the Java way. Make the field nullable and initialize it with null. I don't want to do that.
- Initialize the field with a "default instance" of the type. That's what I currently do. But that would be too expensive for some types.
Can someone recommend a better way (that does not involve removing the custom setter)?
Replace it with a property backed by nullable property:
var _tmp: String? = null
var tmp: String
get() = _tmp!!
set(value) {_tmp=value; println("tmp set to $value")}
or this way, if you want it to be consistent with lateinit
semantics:
var _tmp: String? = null
var tmp: String
get() = _tmp ?: throw UninitializedPropertyAccessException("\"tmp\" was queried before being initialized")
set(value) {_tmp=value; println("tmp set to $value")}
This can be achieved by using a backing property (as per Pavlus's answer); however, I prefer to wrap it inside a delegate to avoid exposing it outside of the property's context:
open class LateInit<T: Any> : ReadWriteProperty<Any?, T> {
protected lateinit var field: T
final override fun getValue(thisRef: Any?, property: KProperty<*>) = get()
final override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = set(value)
open fun get() = field
open fun set(value: T) { field = value }
}
This provides standard getters and setters that can be overridden with a custom implementation:
var upperCaseString by object : LateInit<String>() {
override fun set(value: String) {
field = value.toUpperCase()
}
}
However, since this implementation requires extending the delegate, the generic type cannot be inferred from the property type. This can overcome by taking the custom getter and setter as parameters:
class LateInit<T: Any>(private val getter: FieldHolder<T>.() -> T = { field },
private val setter: FieldHolder<T>.(T) -> Unit = { field = it }) :
ReadWriteProperty<Any?, T> {
private val fieldHolder = FieldHolder<T>()
override fun getValue(thisRef: Any?, property: KProperty<*>) = fieldHolder.getter()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =
fieldHolder.setter(value)
class FieldHolder<T: Any> {
lateinit var field: T
}
}
Which can then be used like this:
private var upperCaseString: String by LateInit(setter = { field = it.toUpperCase() })