Search code examples
androidkotlinimmutabilitylazy-evaluationkotlin-lateinit

Kotlin: lateinit to val, or, alternatively, a var that can set once


Just curious: In Kotlin, I would love to get some val that can be initialized by lazy, but with a parameter. That's because I need something that's created very late in order to initialize it.

Specifically, I wish I had:

private lateinit val controlObj:SomeView

or:

private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}

and then:

override fun onCreateView(....) {
    val view = inflate(....)


    controlObj = view.findViewById(...)

or in the 2nd case controlObj.initWith(view) or something like that:

return view

I cannot use by lazy because by lazy won't accept external parameters to be used when initialising. In this example - the containing view.

Of course I have lateinit var but it would be nice if I could make sure it becomes read only after setting and I could do it in one line.

Is there a pretty clean way to create a read only variable that initializes only once but only when some other variables are born? Any init once keyword? That after init the compiler knows it's immutable?

I am aware of the potential concurrency issues here but if I dare to access it before init, I surely deserve to be thrown.


Solution

  • You can implement own delegate like this:

    class InitOnceProperty<T> : ReadWriteProperty<Any, T> {
    
        private object EMPTY
    
        private var value: Any? = EMPTY
    
        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            if (value == EMPTY) {
                throw IllegalStateException("Value isn't initialized")
            } else {
                return value as T
            }
        }
    
        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
            if (this.value != EMPTY) {
                throw IllegalStateException("Value is initialized")
            }
            this.value = value
        }
    }
    

    After that you can use it as following:

    inline fun <reified T> initOnce(): ReadWriteProperty<Any, T> = InitOnceProperty()
    
    class Test {
    
         var property: String by initOnce()
    
         fun readValueFailure() {
             val data = property //Value isn't initialized, exception is thrown
         }
    
         fun writeValueTwice() {
             property = "Test1" 
             property = "Test2" //Exception is thrown, value already initalized
         }
    
         fun readWriteCorrect() {
             property = "Test" 
             val data1 = property
             val data2 = property //Exception isn't thrown, everything is correct
         }
    
    }
    

    In case when you try to access value before it is initialized you will get exception as well as when you try to reassign new value.