Search code examples
kotlinvariable-assignmentshortcutkotlin-null-safety

Kotlin nullable variable assignment


In Kotlin, is there any shorter syntax for this code:

if(swipeView == null){
     swipeView = view.find<MeasureTypePieChart>(R.id.swipeableView)
}

First i tried this:

 swipeView ?: view.find<MeasureTypePieChart>(R.id.swipeableView)

but then i realised that wasn't an assignment, so that code does nothing. Then i tried:

swipeView = swipeView ?: view.find<MeasureTypePieChart>(R.id.swipeableView)

Which works, but it a bit verbose. I would expect something like this:

swipeView ?= view.find<MeasureTypePieChart>

But unfortunately that doesn't work. Is there any way of accomplish this with a short syntax?

I know i can do this:

variable?.let { it = something } which works.

Solution

  • Shorter syntax would be to avoid swipeView from ever being null.

    Local variable

    If swipeView is a local variable then you can declare it non-null when initially assigning it:

    val swipeView = ... ?: view.find<MeasureTypePieChart>(R.id.swipeableView)
    

    Function argument

    If swipeView is a function argument then you can use a default argument to ensure it is never null:

    fun something(swipeView: View = view.find<MeasureTypePieChart>(R.id.swipeableView))
    

    Class property

    Read-only

    If swipeView is a read-only class property (i.e. val) then you can use Kotlin's built-in Lazy:

    val swipeView by lazy { view.find<MeasureTypePieChart>(R.id.swipeableView) }
    

    Mutable

    If swipeView is a mutable class property (i.e. var) then you can define your own delegate similar to Lazy but mutable. e.g. The following is based on kotlin/Lazy.kt:

    interface MutableLazy<T> : Lazy<T> {
        override var value: T
    }
    
    fun <T> mutableLazy(initializer: () -> T): MutableLazy<T> = SynchronizedMutableLazyImpl(initializer)
    
    fun <T> mutableLazy(lock: Any?, initializer: () -> T): MutableLazy<T> = SynchronizedMutableLazyImpl(initializer, lock)
    
    operator fun <T> MutableLazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
    operator fun <T> MutableLazy<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
    
    private object UNINITIALIZED_VALUE
    
    private class SynchronizedMutableLazyImpl<T>(initializer: () -> T, lock: Any? = null) : MutableLazy<T>, Serializable {
        private var initializer: (() -> T)? = initializer
        @Volatile private var _value: Any? = UNINITIALIZED_VALUE
        // final field is required to enable safe publication of constructed instance
        private val lock = lock ?: this
    
        override var value: T
            get() {
                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
    
                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
            }
            set(value) {
                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    _value = value
                } else synchronized(lock) {
                    _value = value
                    initializer = null
                }
            }
    
        override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    
        override fun toString(): String = if (isInitialized()) value.toString() else "MutableLazy value not initialized yet."
    }
    

    Usage:

    var swipeView by mutableLazy { view.find<MeasureTypePieChart>(R.id.swipeableView) }
    

    The initializer will only be called if swipeView is read and is not initialized yet (from a previous read or write).