Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy variable with reset

Tags:

kotlin

I want to create a variable of a certain type that is not null, say Foo for example.

I then want all access to the variable to return Foo, just like a lazy delegate, however, I also want to be able to reset it.

Something like:

var foo : String by Foo(init: {"bar"})

print(foo) // prints "bar"
foo = null // or foo.reset()
print(foo) // prints "bar"

The problem I am trying to solve: I have an index for an adapter that I need to recreate when the adapter content changes. So on change I want to clear the index, and the next time someone tries to accessing it, I want to recreate it.

like image 976
Heinrisch Avatar asked May 18 '16 08:05

Heinrisch


2 Answers

If the goal is to have a lazy initialized var property which can be reset to it's initial state you can adapt Kotlin's SynchronizedLazyImpl to allow the invalidate feature:

private object UNINITIALIZED_VALUE
class InvalidatableLazyImpl<T>(private val initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    private val lock = lock ?: this
    fun invalidate(){
        _value = UNINITIALIZED_VALUE
    }

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    _v2 as T
                }
                else {
                    val typedValue = initializer()
                    _value = typedValue
                    typedValue
                }
            }
        }


    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    operator fun setValue(any: Any, property: KProperty<*>, t: T) {
        _value = t
    }
}

Which could then be used as follows:

private val fooDelegate = InvalidatableLazyImpl({"bar"})
var foo:String by fooDelegate

println(foo); // -> "bar"
foo = "updated"
println(foo); // -> "updated"
fooDelegate.invalidate()
println(foo); // -> "bar"

One could obviously modify the delegate implementation to allow for null value to act as a reset however it could make the code harder to reason about i.e:

println(obj.foo); //-> prints "bar
obj.foo = null //reset the value, implicitely
println(obj.foo); //-> prints "bar", but hey didn't I just said `null`
like image 56
miensol Avatar answered Sep 25 '22 02:09

miensol


I have the same requirement and wrote LazyEx class as follows. Based on the concept of miensol. LazyEx wraps the original Kotlin's Lazy property instead of adapting it.

fun <T> lazyEx(initializer: () -> T): LazyEx<T> = LazyEx(initializer)

class LazyEx<out T>(private var initializer: () -> T) : Lazy<T> {

    @Volatile
    private var wrap = Wrap()
    override val value: T get() = wrap.lazy.value
    override fun isInitialized() = wrap.lazy.isInitialized()
    override fun toString() = wrap.lazy.toString()
    fun invalidate() { wrap = Wrap() } // create a new Wrap object

    private inner class Wrap { val lazy = lazy(initializer) }
}

usage:

object LazyExTest {
    var i = 0
    val fooDelegate = LazyEx { "bar${i++}" }
    val foo by fooDelegate

    fun run() {
        println(foo)                // -> "bar0"
        println(fooDelegate.value)  // -> "bar0"
        fooDelegate.invalidate()
        println(foo)                // -> "bar1"
        println(fooDelegate.value)  // -> "bar1"
        fooDelegate.invalidate()
        println(fooDelegate.value)  // -> "bar2"
    }
}
like image 42
Ken Wu Avatar answered Sep 26 '22 02:09

Ken Wu