Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin lazy properties and values reset: a resettable lazy delegate

So I use kotlin for android, and when inflating views, I tend to do the following:

private val recyclerView by lazy { find<RecyclerView>(R.id.recyclerView) } 

This method will work. However, there is a case in which it will bug the app. If this is a fragment, and the fragment goes to the backstack, onCreateView will be called again, and the view hierarchy of the fragment will recreated. Which means, the lazy initiated recyclerView will point out to an old view no longer existent.

A solution is like this:

private lateinit var recyclerView: RecyclerView 

And initialise all the properties inside onCreateView.

My question is, is there any way to reset lazy properties so they can be initialised again? I like the fact initialisations are all done at the top of a class, helps to keep the code organised. The specific problem is found in this question: kotlin android fragment empty recycler view after back

like image 683
johnny_crq Avatar asked Mar 02 '16 16:03

johnny_crq


People also ask

What is the difference between Lateinit and lazy in Kotlin?

In the above code, you can see that the object of the HeavyClass is created only when it is accessed and also the same object is there all over the main() function. Lazy is mainly used when you want to access some read-only property because the same object is accessed throughout.

What is lazy property in Kotlin?

Fragment Lifecycle from Android Developer. If you are developing Android apps using Kotlin, lazy property is one of the useful features that helps us save some memory by delaying the initialisation until they are requested and keeping that instance for the rest of the usage.

Is lazy thread safe Kotlin?

It is lazy and thread-safe, it initializes upon first call, much as Java's static initializers. You can declare an object at top level or inside a class or another object.

What is lazy initialization in Kotlin and how do we do it?

lazy initialisation is a delegation of object creation when the first time that object will be called. The reference will be created but the object will not be created. The object will only be created when the first time that object will be accessed and every next time the same reference will be used.


1 Answers

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)    } } 
like image 151
4 revs Avatar answered Sep 30 '22 01:09

4 revs