Kotlin: workaround for no lateinit when using custom setter?

I realized that you can also make your private property lateinit instead of making it nullable:

var tmp: T
    get() = _tmp
    set(value) {
        _tmp = value
        println("tmp set to $value")
    }

private lateinit var _tmp: T

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() })

Replace it with a property backed by nullable property:

private 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:

private 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")}

Tags:

Android

Kotlin