android - lateinit - Restablecimiento de valores y propiedades diferidas de Kotlin: un delegado perezoso reiniciable
android kotlin setter (2)
Así que uso kotlin
para Android, y al inflar las vistas, tiendo a hacer lo siguiente:
private val recyclerView by lazy { find<RecyclerView>(R.id.recyclerView) }
Este método funcionará. Sin embargo, hay un caso en el que se producirá un error en la aplicación. Si se trata de un fragmento y el fragmento va a la columna posterior, onCreateView
volverá a llamar y se volverá a crear la jerarquía de vista del fragmento. Lo que significa que el reciclador Iniciado perezoso señalará una vista anterior que ya no existe.
Una solución es así:
private lateinit var recyclerView: RecyclerView
Y inicialice todas las propiedades dentro onCreateView
.
Mi pregunta es, ¿hay alguna manera de restablecer las propiedades perezosas para que puedan ser inicializadas de nuevo? Me gusta el hecho de que las inicializaciones se hacen en la parte superior de una clase, ayuda a mantener el código organizado. El problema específico se encuentra en esta pregunta: kotlin android fragment empty recycler view after back
Aquí hay una versión rápida de un perezoso reajustable, podría ser más elegante y necesita una verificación doble para la seguridad del hilo, pero esta es básicamente la idea. Necesita algo para administrar (realizar un seguimiento) de los delegados perezosos para que pueda solicitar el reinicio, y luego cosas que se pueden administrar y restablecer. Esto envuelve lazy()
en estas clases de gestión.
Así es como se verá tu clase final , como un ejemplo:
class Something {
val lazyMgr = resettableManager()
val prop1: String by resettableLazy(lazyMgr) { ... }
val prop2: String by resettableLazy(lazyMgr) { ... }
val prop3: String by resettableLazy(lazyMgr) { ... }
}
Luego, para hacer que los perezosos vuelvan a los nuevos valores la próxima vez que se acceda a ellos:
lazyMgr.reset() // prop1, prop2, and prop3 all will do new lazy values on next access
La implementación del perezoso reiniciable:
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()
Y algunas pruebas unitarias para estar seguro:
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)
}
}
Tenía la misma tarea, y esto es lo que utilicé:
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class 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)
uso:
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") }
}
Por supuesto, puede tener sus propias estrategias de almacenamiento en caché.