15

I am trying to get Room(https://developer.android.com/topic/libraries/architecture/room) work with Kotlin's inline classes as described in Jake Whartons article Inline Classes Make Great Database IDs:

@Entity
data class MyEntity(
    @PrimaryKey val id: ID,
    val title: String
)

inline class ID(val value: String)

When compiling this Room complains that

Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).

Looking into the generated Java code I find:

private MyEntity(String id, String title) {
      this.id = id;
      this.title = title;
}

// $FF: synthetic method
public MyEntity(String id, String title, DefaultConstructorMarker $constructor_marker) {
      this(id, title);
}

Mysteriously the default constructor is private now.

When using String as a type for id (or a typealias), the generated Java class constructor looks like expected:

public MyEntity(@NotNull String id, @NotNull String title) {
  Intrinsics.checkParameterIsNotNull(id, "id");
  Intrinsics.checkParameterIsNotNull(title, "title");
  super();
  this.id = id;
  this.title = title;
}

Does somebody now how to keep the default constructor public while using Inline Classes as data entity properties?

K.Os
  • 5,123
  • 8
  • 40
  • 95
whlk
  • 15,487
  • 13
  • 66
  • 96
  • 1
    Did you rise an issue for this on some issue tracker? I guess this need to be clarified for sure – K.Os Nov 24 '19 at 00:48
  • What is the Room version? The latest stable one is 2.3.0 and the latest one is 2.4.0-alpha02 at the time I'm commenting. – Dr.jacky Jun 04 '21 at 15:57
  • [Room 2.6.0-alpha01](https://developer.android.com/jetpack/androidx/releases/room#2.6.0-alpha01) added support for inline / value classes when generating Kotlin code and using KSP. – DanyBoricua May 24 '23 at 11:42

3 Answers3

0

I believe the reason is that the ID class will be represented as String in runtime. So the $constructor_marker additional parameter is to guarantee the uniqueness of the MyEntity(String id, String title) constructor signature, cause this constructor could already have been defined. But I'm just speculating here.

Could you try to explicitly define this constructor in MyEntity class and see if it works?

Diego Marin Santos
  • 1,923
  • 2
  • 15
  • 29
  • This constructor is already defined explicit (but with syntactic sugar) in the `data class MyEntity(@PrimaryKey val id: ID, val title: String)` – whlk Oct 02 '19 at 19:40
  • 1
    You defined a constructor with (ID, String) parameters, I meant a constructor with (String, String). I tried, but it seems like it is not possible. I got a "Platform declaration clash" error. – Diego Marin Santos Oct 02 '19 at 21:12
0

Kotlin inline classes use name mangling.

So I believe your Room database cannot find the getter and setter for you ID field. Try to add:

...
@get:JvmName("getID")
@set:JvmName("setID")
@PrimaryKey val id: ID,

before your ID parameter declaration to disable mangling. It helps to me

0

With the answer from Lyubomyr Ivanitskiy and some tinkering it can be done.

@Entity
class Test(
    @PrimaryKey(autoGenerate = true)
    @get:JvmName("getId")
    @set:JvmName("setId")
    var id: ID,
) {
    constructor(): this(ID(0)) // This is required as replacement for 
    constructor with actual fields
}

When trying to load this entity using a dao it will fail due to the getter method not being generated. It does not work for me using the inner class ID. So it needs to be tricked like this:

@Dao
interface TheDao {
    @Deprecated(
        "This is just for the generated Dao_Impl",
        level = DeprecationLevel.WARNING,
        replaceWith = ReplaceWith("getByIdRealId(theID)")
    )
    @Query("select * from test where id = :theID")
    fun getByIdLongType(theID: Long): Test

}

fun TheDao.getByIdRealId(theID: ID): Test = getByIdLongType(theID.id)

This will not prevent using the getById with Long parameter but generate at least a warning about it.

TestCode:

@Test
fun createAndLoadTest() {
    val toBeSaved = Test(ID(42))
    dao.save(toBeSaved)
    val fromDB = dao.getByIdRealId(ID(42))
    fromDB shouldNotBe null
    fromDB.id shouldNotBe 42
    fromDB.id shouldBe ID(42)
}
MoYapro
  • 119
  • 1
  • 8