How to create a nullable delegate property?


#1

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;
    }
}

but I would like a pure Kotlin implementation.


#2

Use “T?” instead “T”


#3

Can you show me full example? I don’t understand you. I want to be able to use non-nullable properties too, like person.name in my example.


#4

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
    }
}

#5

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.


#6

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
  }
}