Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set lateinit Kotlin property to null

The below class has a very unique lifecycle, which requires me to temporarily null out lateinit properties

class SalesController : BaseController, SalesView {
    @Inject lateinit var viewBinder: SalesController.ViewBinder
    @Inject lateinit var renderer: SalesRenderer
    @Inject lateinit var presenter: SalesPresenter
    lateinit private var component: SalesScreenComponent

    override var state = SalesScreen.State.INITIAL  //only property that I want to survive config changes

    fun onCreateView(): View {  /** lateinit variables are set here */ }
    fun onDestroyView() {
         //lateinit variables need to be dereferences here, or we have a memory leak 
         renderer = null!! //here's the problem: throws exception bc it's a non-nullable property

} }

Here's how it's used by the framework.

controller.onCreateView() //same instance of controller
controller.onDestroyView() //same instance of controller
controller.onCreateView() //same instance of controller
controller.onDestroyView() //same instance of controller

My lateinit properties are injected by dagger, and I need to set them to null in onDestroyView - or have a memory leak. This however is not possible in kotlin, as far as I am aware (without reflection). I could make these properties nullable, but that would defeat the purpose of Kotlin's null safety.

I'm not quite sure how to solve this. Ideally there could be some type of annotation processor that would generate java code to null out specific variables automatically in onDestroyView?

like image 620
ZakTaccardi Avatar asked Feb 22 '17 16:02

ZakTaccardi


People also ask

Can Lateinit be null?

While using Lateinit , the variable can't be of the null type. Lateinit cannot be used for primitive datatypes, i.e., Long and int. If you try accessing Lateinit variables without initializing, it will throw an exception stating that it is not initialized or properly being accessed.

How do you Uninitialize Lateinit variable in Kotlin?

lateinit var is only for first time initialization, for cases when you know that your variable is not nullable but you cannot initialize it in constructor(for example in android framework). There is no point in having lateinit var if you "uninitialize" it after. Kotlin does not allow this kind of manipulation.

How do you check Lateinit property is initialized Kotlin?

In order to avoid this situation, Kotlin introduced a new modifier called as "lateInit". Along with this modifier, Kotlin provides a couple of methods to check whether this variable is initialized or not. Use "lateInit" with a mutable variable. That means, we need use "var" keyword with "lateInit".


1 Answers

Kotlin lateinit properties use null as an uninitialized flag value, and there's no clean way to set null in the backing field of a lateinit property without reflection.


However, Kotlin allows you to override the properties behavior using delegated properties. Seems like there's no delegate that allows that in kotlin-stdlib, but if you need exactly this behavior, you can implement your own delegate to do that, adding some code to your utils:

class ResettableManager {
    private val delegates = mutableListOf<ResettableNotNullDelegate<*, *>>()

    fun register(delegate: ResettableNotNullDelegate<*, *>) { delegates.add(delegate) }

    fun reset() { delegatesToReset.forEach { it.reset() } }
}

class Resettable<R, T : Any>(manager: ResettableManager) {
    init { manager.register(this) }

    private var value: T? = null

    operator fun getValue(thisRef: R, property: KProperty<*>): T =
            value ?: throw UninitializedPropertyAccessException()

    operator fun setValue(thisRef: R, property: KProperty<*>, t: T) { value = t }

    fun reset() { value = null }
}

And the usage:

class SalesController : BaseController, SalesView {
    val resettableManager = ResettableManager()
    @set:Inject var viewBinder: SalesController.ViewBinder by Resettable(resettableManager)
    @set:Inject var renderer: SalesRenderer by Resettable(resettableManager)
    @set:Inject var presenter: SalesPresenter by Resettable(resettableManager)

    fun onDestroyView() {
        resettableManager.reset()
    }
}
like image 73
hotkey Avatar answered Jan 01 '23 22:01

hotkey