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?
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()
}
}
Related
If I use a primary constructor to initialize a property, its setter won't be called:
open class Foo(open var bar: Any)
open class Baz(bar: Any) : Foo(bar) {
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
...
Baz(1) // works, no setter is called
This works too:
open class Foo(bar: Any) {
open var bar: Any = bar // no exception in Baz(1)
}
But this doesn't:
open class Foo {
open var bar: Any
constructor(bar: Any) {
this.bar = bar // UnsupportedOperationException in Baz(1)
}
}
This can't even be compiled:
open class Foo(bar: Any) {
open var bar: Any // Property must be initialized or be abstract
init {
this.bar = bar
}
}
I'm asking because I need to make bar lateinit to be able doing this:
Baz(1) // works
Foo(2) // this too
Foo().bar = 3 // should work too
But this doesn't work in Kotlin:
// 'lateinit' modifier is not allowed on primary constructor parameters
open class Foo(open lateinit var bar: Any)
And there's no syntax like this:
open class Foo() {
open lateinit var bar: Any
constructor(bar: Any) {
// instead of this.bar
fields.bar = bar // or whatever to bypass the setter
}
}
Any ideas how to solve this? (besides reflection, adding extra properties and delegates)
To allow existence of Foo() object you need to provide some init value for bar property (or make it lateinit). But open lateinit property can't be initialized in constructor/init block, because it leads to leaking 'this' in constructor.
So there are 2 options:
Use lateinit modifier and provide factory function instead of secondary constructor for Foo (also requires modification of Baz class):
open class Foo {
open lateinit var bar: Any
}
fun Foo(bar: Any): Foo = Foo().also { it.bar = bar }
open class Baz(bar: Any) : Foo() {
init {
super.bar = bar
}
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
Use some dummy default value for bar property and add custom getter to emulate lateinit property behavior:
open class Foo(bar: Any = NONE) {
companion object {
private object NONE
}
open var bar = bar
get() = field.takeIf { it != NONE } ?: throw UninitializedPropertyAccessException()
}
However note, that making possible to set property in the base class, but disallowing it in the derived is a poor class design, because it breaks Liskov substitution principle.
I have a class, A, that needs to be marked as dirty anytime one of its properties is changed.
After reviewing the Kotlin docs, I know I need a delegate. So far I have:
abstract class CanBeDirty {
var isDirty = false
}
class A(
// properties getting set in constructor here
) : CanBeDirty {
var property1: String by DirtyDelegate()
var property2: Int by DirtyDelegate()
var property3: CustomObject by DirtyDelegate()
}
class DirtyDelegate() {
operator fun getValue(thisRef: CanBeDirty, property: KProperty<*>): Resource {
return valueOfTheProperty
}
operator fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: Any?) {
if (property != value) {
thisRef.isDirty = true
//set the value
}
else {
//don't set the value
}
}
}
I believe the lack of setting has something to do with vetoable() but the examples I see in Kotlin documentation don't really show me how to do this with a fully formed class Delegate (and I'm just not that up to speed on Kotlin syntax, honestly).
Your delegate class needs its own property to store the value it will return. And if you don't want to deal with uninitialized values, it should also have a constructor parameter for the initial value. You don't have to implement ReadWriteProperty, but it allows the IDE to autogenerate the correct signature for the two operator functions.
class DirtyDelegate<T>(initialValue: T): ReadWriteProperty<CanBeDirty, T> {
private var _value = initialValue
override fun getValue(thisRef: CanBeDirty, property: KProperty<*>): T {
return _value
}
override fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: T) {
if (_value != value) {
_value = value
thisRef.isDirty = true
}
}
}
Since this takes an initial value parameter, you have to pass it to the constructor:
class A: CanBeDirty() {
var property1: String by DirtyDelegate("")
var property2: Int by DirtyDelegate(0)
var property3: CustomObject by DirtyDelegate(CustomObject())
}
If you wanted to set an initial value based on something passed to the constructor, you could do:
class B(initialName: String): CanBeDirty() {
var name by DirtyDelegate(initialName)
}
Is there any way to pass this when using interface delegation? This would enable nice composability - but I found no way to do this.
Means something like:
interface Foo {
}
class FooImpl(bar: Bar) : Foo {
}
class Bar: Foo by FooImpl(this) {
}
as long as FooImpl doesnt need a parameter like this it works - but it would be great to access the other class there - perhaps someone knows a way. Otherwise I would also be interested if this is worth a KEEP if not - or if it will be impossible for some reason.
Delegation doesn't support this. The delegate has to be instantiated before the class that is delegating to it, so the delegate cannot rely on it for construction. Another gotcha is that although you can override functions of the delegate, if the delegate internally calls those functions, it calls the original version, not the override. The delegate really lives in its own world.
But you could set it up for the host to pass itself to the delegate in its initialization block:
interface Foo<T> {
var host: T
fun doSomething()
}
class FooImpl : Foo<Bar> {
override lateinit var host: Bar
override fun doSomething() {
println(host.name)
}
}
class Bar(val name: String): Foo<Bar> by FooImpl() {
init {
host = this
}
}
fun main() {
val bar = Bar("Hello world")
bar.doSomething()
}
This would unfortunately expose the host to the possibility of getting disconnected from its own delegate by outside classes, though. Maybe you could make the property throw an exception if assigned more than once.
Here's a property delegate that could do that:
private class SingleAssignmentVar<T>: ReadWriteProperty<Any, T> {
private var value: T? = null
private var assigned: Boolean = false
#Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (!assigned)
error("Property has not yet been set.")
return value as T
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
if (assigned)
error("Property may only be set once.")
assigned = true
this.value = value
}
}
fun <T> Delegates.singleAssignment(): ReadWriteProperty<Any, T> = SingleAssignmentVar()
You may split your Bar class in two parts, say backend and frontend.
Frontend will be responsible for declaring interface with delegates, backend will host delegates and act as composition target.
For example:
interface Foo {
fun sayHello(): String
}
class FooImpl(val bar: BarBackend) : Foo {
override fun sayHello() = "Hello from ${bar.compositionTarget()}!"
}
class BarBackend() {
val fooImpl: FooImpl = FooImpl(this)
fun compositionTarget() = "backend"
}
class BarFrontend(backend: BarBackend) : Foo by backend.fooImpl
fun main() {
val bar = BarFrontend(BarBackend())
println(bar.sayHello())
}
I have a singleton class which i have implemented it in java fashion :
companion object {
#Volatile private lateinit var instance: TrapBridge
fun bridge(): TrapBridge {
if (!this::instance.isInitialized) {
synchronized(this) {
if (!this::instance.isInitialized) {
instance = TrapBridge()
}
}
}
return instance
}
}
now the problem is i can't use isInitialized property because it throws NoSuchFieldError exception :
java.lang.NoSuchFieldError: No field instance of type Lcom/sample/trap/model/TrapBridge; in class Lcom/sample/trap/model/TrapBridge$Companion; or its superclasses (declaration of 'com.sample.trap.model.TrapBridge$Companion' appears in /data/app/com.sample.trapsample-L9D8b2vxEQfiSg9Qep_eNw==/base.apk)
and i think it's because instance is class variable and not instance variable so i can't reference it with this keyword :/
also i can't check if it's null because it throws UninitializedPropertyAccessException :
lateinit property instance has not been initialized
Unfortunately this is a known issue, tracked here on the official Kotlin issue tracker.
I had the same issue and found another way to do this:
#Volatile
private lateinit var box: BoxStore
class BoxKeeper private constructor() {
companion object {
var instance: BoxStore
get() = box
set(_) {}
fun init(context: Context) {
if (::box.isInitialized.not())
box = MyObjectBox.builder().androidContext(context).build()
}
}
}
Can you delegate a property to another property in Kotlin? I have the following code:
class SettingsPage {
lateinit var tagCharacters: JTextField
lateinit var tagForegroundColorChooser: ColorPanel
lateinit var tagBackgroundColorChooser: ColorPanel
var allowedChars: String
get() = tagCharacters.text
set(value) = tagCharacters.setText(value)
var tagForegroundColor by tagForegroundColorChooser
var tagBackgroundColor by tagBackgroundColorChooser
}
In order to get property delegation, I declare the following two extension functions:
operator fun ColorPanel.getValue(a: SettingsPage, p: KProperty<*>) = selectedColor
operator fun ColorPanel.setValue(a: SettingsPage, p: KProperty<*>, c: Color?) { selectedColor = c }
However, what I would like to write is something like the following:
class SettingsPage {
lateinit var tagCharacters: JTextField
lateinit var tagForegroundColorChooser: ColorPanel
lateinit var tagBackgroundColorChooser: ColorPanel
var allowedChars: String by Alias(tagCharacters.text)
var tagForegroundColor by Alias(tagForegroundColorChooser.selectedColor)
var tagBackgroundColor by Alias(tagBackgroundColorChooser.selectedColor)
}
Is this possible to do Kotlin? How do I write the class Alias?
UPD: Since Kotlin 1.4, the standard library includes the necessary extensions that allow this out of the box:
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
Yes, it's possible: you can use a bound callable reference for a property that you store in the alias, and then the Alias implementation will look like this:
class Alias<T>(val delegate: KMutableProperty0<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
delegate.get()
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
delegate.set(value)
}
}
And the usage:
class Container(var x: Int)
class Foo {
var container = Container(1)
var x by Alias(container::x)
}
To reference a property of the same instance, use this::someProperty.
Follow-up, if you would like to see support for property references in Kotlin, please vote and track this issue for updates: https://youtrack.jetbrains.com/issue/KT-8658