19

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?

Lior Bar-On
  • 10,784
  • 5
  • 34
  • 46
ZakTaccardi
  • 12,212
  • 15
  • 59
  • 107
  • why you have a leak? maybe the problem is on the SalesController rather than on its properties? I've never needed to explicitly set to null a property injected by Dagger to avoid leak problems... – Massimo Feb 22 '17 at 17:08
  • @Massimo Conductor's controller instances survive config changes https://github.com/bluelinelabs/Conductor – ZakTaccardi Feb 22 '17 at 17:18
  • If you need to nullify them, you don't need to have `lateinit` then. And I'm pretty sure you are don't have any leak there, you are just mixing some definitions. You would leak if your presenter would reference your View, not vice-versa – Dimezis Feb 24 '17 at 17:17

1 Answers1

11

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()
    }
}
hotkey
  • 140,743
  • 39
  • 371
  • 326