Search code examples
androidkotlinlazy-initialization

Dynamic lazy initialization for val in kotlin


I understand there are two ways for lazy initialization in kotlin. first by lateinit which is dynamic but it is only for var. second, by lazy delegate which is for val but it is static, which means it can't be initialized at runtime.
I was wondering is there a way to have lazy dynamic initialization for immutable properties(val)????

property delegation also works like lazy and even if we define a custom delegate, its always static initialization. (to my knowledge)

is there a workaround for this? could it be implemented somehow?
so what I wish for, is something like lateinit val, shown in below code:

class MyClass: SomeCallback {
    
    private lateinit val myData: String
    
    override fun onStatusChanged(status: Status, data: String) {
        if(status == Status.DataConfirmed ) {
            myData = data
        }
    }

}

Solution

  • The best I can come up with is a read-write property delegate that throws if you access it before setting it, or if you set it multiple times. Kotlin doesn't let you lateinit a val. This is likely because it would be nonsensical to call a setter for a property that doesn't have one. I doubt they want to introduce the can of worms it would be to directly set the value of a backing field from anywhere besides the initializer, because it would be ambiguous.

    A delegate like this should be adequate. If it's not adequate to help you immediately fix the bug of calling the setter multiple times, I would say that's a code smell that the class is too complicated and needs to be broken up into smaller units.

    class Once<T>: ReadWriteProperty<Any, T> {
        private object UNINITIALIZED
        private var _value: Any? = UNINITIALIZED
    
        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            if (_value !== UNINITIALIZED) {
                @Suppress("UNCHECKED_CAST")
                return _value as T
            }
            throw UninitializedPropertyAccessException("Property [$property] was accessed before it was initialized.")
        }
    
        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
            if (_value === UNINITIALIZED) {
                _value = value
            } else {
                error("Cannot set property [$property] more than once.")
            }
        }
    }