将 Kotlin 内联类作为实体字段的房间数据库

Posted

技术标签:

【中文标题】将 Kotlin 内联类作为实体字段的房间数据库【英文标题】:Room database with Kotlin inline class as an Entity field 【发布时间】:2020-01-31 21:02:48 【问题描述】:

我正在尝试让 Room(https://developer.android.com/topic/libraries/architecture/room) 与 Kotlin 的内联类一起工作,如 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)

编译这个房间时抱怨

实体和 Pojos 必须有一个可用的公共构造函数。你可以有 空构造函数或参数匹配的构造函数 字段(按名称和类型)。

查看我发现的生成的 Java 代码:

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

神奇的是,默认构造函数现在是私有的。

当使用String 作为id(或typealias)的类型时,生成的Java 类构造函数看起来像预期的那样:

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

现在有人在使用内联类作为数据实体属性时如何保持默认构造函数公开?

【问题讨论】:

您是否在某个问题跟踪器上为此提出了问题?我想这肯定需要澄清 房间版是什么?在我发表评论时,最新的稳定版是 2.3.0,最新的是 2.4.0-alpha02。 【参考方案1】:

我相信原因是 ID 类将在运行时表示为 String。所以 $constructor_marker 附加参数是为了保证 MyEntity(String id, String title) 构造函数签名的唯一性,因为这个构造函数可能已经被定义了。但我只是在这里推测。

您能否尝试在 MyEntity 类中显式定义此构造函数并查看它是否有效?

【讨论】:

这个构造函数已经在data class MyEntity(@PrimaryKey val id: ID, val title: String)中明确定义(但带有语法糖) 你定义了一个带有 (ID, String) 参数的构造函数,我的意思是一个带有 (String, String) 的构造函数。我试过了,但似乎不可能。我收到“平台声明冲突”错误。【参考方案2】:

Kotlin 内联类使用名称修饰。

所以我相信您的 Room 数据库无法为您的 ID 字段找到 getter 和 setter。 尝试添加:

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

在您的 ID 参数声明之前禁用重整。 对我有帮助

【讨论】:

【参考方案3】:

通过 Lyubomyr Ivanitskiy 的回答和一些修补,它可以完成。

@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

当尝试使用 dao 加载此实体时,由于未生成 getter 方法,它将失败。使用内部类 ID 对我不起作用。所以它需要像这样被欺骗:

@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)

这不会阻止使用带有 Long 参数的 getById,但至少会生成一个警告。

测试代码:

@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)

【讨论】:

以上是关于将 Kotlin 内联类作为实体字段的房间数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Kotlin 内联类与 Android 数据绑定集成?

房间持久性:错误:实体和 Pojos 必须有一个可用的公共构造函数

如何填写房间库中实体的非列字段

Kotlin 数据类的房间数据库错误

Kotlin函数 ⑦ ( 内联函数 | Lambda 表达式弊端 | “ 内联 “ 机制避免内存开销 - 将使用 Lambda 表达式作为参数的函数定义为内联函数 | 内联函数本质 - 宏替换 )

把一个实体类作为一个字段的类型怎么给这个字段赋值