Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin delegates with Room persistence lib

I am currently developing a new android app using Kotlin. I tried implementing Room for data storage, but I didn't get it to work with Kotlin delegates.

I created an Identifier delegate in order to ensure the id is not changed after initialization. The delegate looks like this:

class Identifier: ReadWriteProperty<Any?, Long> {

    private var currentValue = -1L

    override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
        if (currentValue == -1L) throw IllegalStateException("${property.name} is not initialized.")
        return currentValue
    }

    override fun setValue(thisRef: Any?, property KProperty<*>, value: Long) {
        if (currentValue != -1L) throw IllegalStateException("${property.name} can not be changed.")
        currentValue = value
    }
}

My entity class looks like this:

@Entity
class Sample {

    @delegate:PrimaryKey(autoGenerate = true)
    var id by Identifier()
}

When I try to start the app, kapt gives me the following error message:

Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final com.myapp.persistence.delegates.Identifier id$delegate = null;

Can I somehow get this to work without writing a TypeConverter for every delegate?

like image 940
Simon Schiller Avatar asked Nov 14 '17 09:11

Simon Schiller


2 Answers

Use @delegate:Ignore.

I had similar problem with my Entity Object and ... by lazy properties.

For example:

var name: String = "Alice"

val greeting: String by lazy { "Hi $name" }

The issue here is Room "cannot figure out how to save this field into database". I tried to add "@Ignore" but got a lint message saying "This annotation is not applicable to target 'member property with delegate'."

Turns out, the correct annotation in this case is @delegate:Ignore.

Solution:

var name: String = "Alice"

@delegate:Ignore
val greeting: String by lazy { "Hi $name" }
like image 196
user1032613 Avatar answered Oct 05 '22 11:10

user1032613


Unfortunatelly, no - Room by default creates a column for each field that's defined in the entity and when we use delegate we get generated code like this:

   @PrimaryKey(autoGenerate = true)
   @NotNull
   private final Identifier id$delegate = new Identifier();

   public final long getId() {
      return this.id$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setId(long var1) {
      this.id$delegate.setValue(this, $$delegatedProperties[0], var1);
   }

and that's why Room tries to create column for Identifier id$delegate.

However, if you just want to ensure id is not changed after object initialization you don't need delegate at all, simply mark variable as final and place it in constructor eg:

@Entity
data class Sample(
    @PrimaryKey(autoGenerate = true)
    val id: Long
)
like image 20
Michał Baran Avatar answered Oct 05 '22 13:10

Michał Baran