I want to create a delegate class which can represent a nullable delegate property. It should be initialized before use. Here’s my attempt:
fun main(args: Array<String>) {
val person = Person()
person.name = "John"
person.address = null
println(person.address)
}
class Person {
var name: String by MyProperty()
var address: String? by MyProperty()
var age: Int? by MyProperty()
}
class MyProperty<T> : ReadWriteProperty<Any, T> {
var value: T? = null
var initialized = false
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (!initialized) throw UninitializedPropertyAccessException(property.name)
return value!!
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
this.value = value
initialized = true
}
}
The problem is !! in getValue method. It’s required to compile, but it throws NullPointerException for given code. It should work without exceptions, null is a valid value for this field, if it was initialized with null. I can’t figure out how to make this code work both for nullable and non-nullable types.
Actually I was able to make this work using Java implementation:
public class MyProperty<T> implements ReadWriteProperty<Object, T> {
private T value;
private boolean initialized;
@Override
public T getValue(Object o, KProperty<?> kProperty) {
if (!initialized) throw new UninitializedPropertyAccessException(kProperty.getName());
return value;
}
@Override
public void setValue(Object o, KProperty<?> kProperty, T value) {
this.value = value;
initialized = true;
}
}
I hit this same problem, and couldn’t find a single-class solution… in the end, I made two classes, something like this:
fun main(args: Array<String>) {
val person = Person()
person.name = "John"
person.address = null
println(person.address)
}
class Person {
var name: String by MyNonNullProperty()
var address: String? by MyNullableProperty()
var age: Int? by MyNullableProperty()
}
class MyNullableProperty<T> : ReadWriteProperty<Any, T?> {
var value: T? = null
var initialized = false
override fun getValue(thisRef: Any, property: KProperty<*>): T? {
if (!initialized) throw UninitializedPropertyAccessException(property.name)
return value
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
this.value = value
initialized = true
}
}
class MyNonNullProperty<T: Any> : ReadWriteProperty<Any, T> {
lateinit var value: T
var initialized = false
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (!initialized) throw UninitializedPropertyAccessException(property.name)
return value
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
this.value = value
initialized = true
}
}
Thanks, that’s a good solution. Another advantage is that I can optimize storage of non-null properties by using null value for indicating uninitialized properties.
With provideDelegate feature and unchecked cast I was able to make it as a universal solution:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class Person {
var name: String by DaoProperty()
var address: String? by DaoProperty()
var age: Int by DaoProperty()
var height: Int? by DaoProperty()
}
class DaoProperty<T> {
operator fun provideDelegate(thisRef: Any, property: KProperty<*>): ReadWriteProperty<Any, T> {
val result: Any = if (property.returnType.isMarkedNullable) NullableDaoProperty<T>() else NotNullDaoProperty<T>()
@Suppress("UNCHECKED_CAST")
return result as ReadWriteProperty<Any, T>
}
}
private class NotNullDaoProperty<T> : ReadWriteProperty<Any, T> {
private var value: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (value == null) throw UninitializedPropertyAccessException(property.name)
return value!!
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
this.value = value
}
}
private class NullableDaoProperty<T> : ReadWriteProperty<Any, T?> {
private var value: T? = null
private var initialized = false
override fun getValue(thisRef: Any, property: KProperty<*>): T? {
if (!initialized) throw UninitializedPropertyAccessException(property.name)
return value
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
this.value = value
this.initialized = true
}
}