Kotlin lazy properties and values reset: a resettable lazy delegate Kotlin lazy properties and values reset: a resettable lazy delegate android android

Kotlin lazy properties and values reset: a resettable lazy delegate


Here is a quick version of a resettable lazy, it could be more elegant and needs double checked for thread safety, but this is basically the idea. You need something to manage (keep track) of the lazy delegates so you can call for reset, and then things that can be managed and reset. This wraps lazy() in these management classes.

Here is what your final class will look like, as an example:

class Something {    val lazyMgr = resettableManager()    val prop1: String by resettableLazy(lazyMgr) { ... }    val prop2: String by resettableLazy(lazyMgr) { ... }    val prop3: String by resettableLazy(lazyMgr) { ... }}

Then to make the lazy's all go back to new values on next time they are accessed:

lazyMgr.reset() // prop1, prop2, and prop3 all will do new lazy values on next access

The implementation of the resettable lazy:

class ResettableLazyManager {    // we synchronize to make sure the timing of a reset() call and new inits do not collide    val managedDelegates = LinkedList<Resettable>()    fun register(managed: Resettable) {        synchronized (managedDelegates) {            managedDelegates.add(managed)        }    }    fun reset() {        synchronized (managedDelegates) {            managedDelegates.forEach { it.reset() }            managedDelegates.clear()        }    }}interface Resettable {    fun reset()}class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()->PROPTYPE): Resettable {    @Volatile var lazyHolder = makeInitBlock()    operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE {        return lazyHolder.value    }    override fun reset() {        lazyHolder = makeInitBlock()    }    fun makeInitBlock(): Lazy<PROPTYPE> {        return lazy {            manager.register(this)            init()        }    }}fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy<PROPTYPE> {    return ResettableLazy(manager, init)}fun resettableManager(): ResettableLazyManager = ResettableLazyManager()

And some unit tests to be sure:

class Tester {   @Test fun testResetableLazy() {       class Something {           var seed = 1           val lazyMgr = resettableManager()           val x: String by resettableLazy(lazyMgr) { "x ${seed}" }           val y: String by resettableLazy(lazyMgr) { "y ${seed}" }           val z: String by resettableLazy(lazyMgr) { "z $x $y"}       }       val s = Something()       val x1 = s.x       val y1 = s.y       val z1 = s.z       assertEquals(x1, s.x)       assertEquals(y1, s.y)       assertEquals(z1, s.z)       s.seed++ // without reset nothing should change       assertTrue(x1 === s.x)       assertTrue(y1 === s.y)       assertTrue(z1 === s.z)       s.lazyMgr.reset()       s.seed++ // because of reset the values should change       val x2 = s.x       val y2 = s.y       val z2 = s.z       assertEquals(x2, s.x)       assertEquals(y2, s.y)       assertEquals(z2, s.z)       assertNotEquals(x1, x2)       assertNotEquals(y1, y2)       assertNotEquals(z1, z2)       s.seed++ // but without reset, nothing should change       assertTrue(x2 === s.x)       assertTrue(y2 === s.y)       assertTrue(z2 === s.z)   }}


I find a convenient method:

import java.util.concurrent.atomic.AtomicReferenceimport kotlin.reflect.KPropertyfun <T> resetableLazy(initializer: () -> T) = ResetableDelegate(initializer)class ResetableDelegate<T>(private val initializer: () -> T) {    private val lazyRef: AtomicReference<Lazy<T>> = AtomicReference(        lazy(            initializer        )    )    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {        return lazyRef.get().getValue(thisRef, property)    }    fun reset() {        lazyRef.set(lazy(initializer))    }}

test:

import org.junit.Assertimport org.junit.Testclass ResetableLazyData {    var changedData = 0    val delegate = resetableLazy { changedData }    val readOnlyData by delegate}class ResetableLazyTest {    @Test    fun testResetableLazy() {        val data = ResetableLazyData()        data.changedData = 1        Assert.assertEquals(data.changedData, data.readOnlyData)        data.changedData = 2        Assert.assertNotEquals(data.changedData, data.readOnlyData)        data.delegate.reset()        Assert.assertEquals(data.changedData, data.readOnlyData)        data.changedData = 3        Assert.assertNotEquals(data.changedData, data.readOnlyData)    }}


I had the same task, and this is what I used:

import kotlin.properties.ReadOnlyPropertyimport kotlin.reflect.KPropertyclass SingletonLazy<T : Any>(val initBlock: () -> T, val clazz: Class<T>) {    operator fun <R> provideDelegate(ref: R, prop: KProperty<*>): ReadOnlyProperty<R, T> = delegate()    @Suppress("UNCHECKED_CAST")    private fun <R> delegate(): ReadOnlyProperty<R, T> = object : ReadOnlyProperty<R, T> {        override fun getValue(thisRef: R, property: KProperty<*>): T {            val hash = clazz.hashCode()            val cached = singletonsCache[hash]            if (cached != null && cached.javaClass == clazz) return cached as T            return initBlock().apply { singletonsCache[hash] = this }        }    }}private val singletonsCache = HashMap<Int, Any>()fun <T> clearSingleton(clazz: Class<T>) : Boolean {    val hash = clazz.hashCode()    val result = singletonsCache[hash]    if (result?.javaClass != clazz) return false    singletonsCache.remove(hash)    return true}inline fun <reified T : Any> singletonLazy(noinline block: () -> T): SingletonLazy<T>        = SingletonLazy(block, T::class.java)

usage:

val cat: Cat by singletonLazy { Cat() }fun main(args: Array<String>) {    cat    println(clearSingleton(Cat::class.java))    cat // cat will be created one more time    println(singletonsCache.size)}class Cat {    init { println("creating cat") }}

Of course, you may have you own caching strategies.