找不到字段的设置器 - 将 Kotlin 与 Room 数据库结合使用

Posted

技术标签:

【中文标题】找不到字段的设置器 - 将 Kotlin 与 Room 数据库结合使用【英文标题】:Cannot find setter for field - using Kotlin with Room database 【发布时间】:2017-10-28 01:09:42 【问题描述】:

我正在与 Room 持久性库集成。我在 Kotlin 中有一个数据类,例如:

@Entity(tableName = "story")
data class Story (
        @PrimaryKey val id: Long,
        val by: String,
        val descendants: Int,
        val score: Int,
        val time: Long,
        val title: String,
        val type: String,
        val url: String
)

@Entity@PrimaryKey 注释用于 Room 库。当我尝试构建时,它失败并出现错误:

Error:Cannot find setter for field.
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

我也尝试过提供默认构造函数:

@Entity(tableName = "story")
data class Story (
        @PrimaryKey val id: Long,
        val by: String,
        val descendants: Int,
        val score: Int,
        val time: Long,
        val title: String,
        val type: String,
        val url: String
) 
    constructor() : this(0, "", 0, 0, 0, "", "", "")

但这也不起作用。需要注意的是,如果我将这个 Kotlin 类转换为带有 getter 和 setter 的 Java 类,它就可以工作。任何帮助表示赞赏!

【问题讨论】:

在谷歌示例中的github.com/googlesamples/android-architecture-components/blob/… 中,不可变属性可以正常工作。有人可以分析原因吗?会不会是bug? 【参考方案1】:

只需使变量可变,将 val 更改为 var 对于 Kotlin,或者将 private 更改为 public 对于 Java

【讨论】:

【参考方案2】:

您可以尝试将 id 变量重命名为另一个名称。它对我有用;

var id: Long? = null

var workerId: Long? = null

如果你必须命名为 id 并且你正在使用改造,那么你可能需要添加 SerializedName("id")

【讨论】:

【参考方案3】:

如果数据类构造函数中有@Ignore 字段,则需要将其移动到类主体,如下所示:

@Entity(primaryKeys = ["id"])
data class User(
        @field:SerializedName("id")
        val id: Int,

        @field:SerializedName("name")
        val name: String,
    
        @field:SerializedName("age")
        val age: Int
) 
    @Ignore
    val testme: String?

所有荣誉都转到 GitHub 上的 marianperca:https://github.com/android/architecture-components-samples/issues/421#issuecomment-442763610

【讨论】:

【参考方案4】:

如果您的列以 Is 开头,则会引发此错误:

@ColumnInfo(name = "IsHandicapLeague")
    @NonNull
    var isHandicapLeague: String = "Y"

添加一个默认的set()函数来消除

fun setIsHandicapLeague(flag:String) 
    isHandicapLeague = flag

【讨论】:

【参考方案5】:

由于您的字段标有val,因此它们实际上是最终的并且没有设置器字段。

尝试将val 替换为var。 您可能还需要初始化字段。

@Entity(tableName = "story")
data class Story (
        @PrimaryKey var id: Long? = null,
        var by: String = "",
        var descendants: Int = 0,
        var score: Int = 0,
        var time: Long = 0L,
        var title: String = "",
        var type: String = "",
        var url: String = ""
)

编辑

上述解决方案是对 Kotlin 中此错误的一般修复,当我将 Kotlin 与其他 Java 库(如 Hibernate)一起使用时,我也看到了这一点。如果您想保持 Room 的不变性,请参阅其他一些可能更适合您的情况的答案。

在某些情况下,Java 库的不变性根本无法正常工作,并且在发出可悲的开发人员噪音的同时,不幸的是,您必须将 val 切换为 var

【讨论】:

嘿,我觉得这很奇怪,你能看一下吗? ***.com/q/47323883/4620609 感谢您的指点,这个问题对我来说大约需要 2 小时才能解决.....我使用了“private var”并且它抛出了错误,没有任何线索 答案的意思是“不要使用 Room,它会迫使你编写糟糕的代码”,对吧? 请注意,在数据类中使用 var 会导致有效的不变性损失。 这个解决方案对我有用,但对于其他有同样问题的人。我还必须删除我的私有可见性修饰符。【参考方案6】:

只需使用 var 而不是 val,如果您使用的是 private 关键字,请将其设为 public。

@Entity(tableName = "story")
data class Story (
        @PrimaryKey val id: Long,
        var by: String,
        var descendants: Int,
        var score: Int,
        var time: Long,
        var title: String,
        var type: String,
        var url: String
)

【讨论】:

考虑不变性【参考方案7】:

我发现此编译错误的另一个原因可能是由于在实体数据类的字段上使用了 Room 的 @Ignore 注释:

@Entity(tableName = "foo")
data class Foo(

    // Okay
    @PrimaryKey
    val id: String,

    // Okay
    val bar: String,

    // Annotation causes compilation error, all fields of data class report
    // the "Cannot find setter for field" error when Ignore is present
    @Ignore
    val causeserror: String
)

使用@Transient 注释时似乎也会发生同样的错误。

我在使用 Room 的 2.2.2 版本时注意到了这个问题:

// build.gradle file
dependencies 
   ...
   kapt "androidx.room:room-compiler:2.2.2"
   ...

希望对某人有所帮助!

【讨论】:

您找到解决方案了吗?卡在同一个案子上。谢谢 就我而言,我需要从实体类的字段中删除@Ignore。也许您的一个或多个实体类的字段中包含@Ignore 显然,Room 库无法实例化您的数据类的对象,因为数据类必须有一个数据库中不存在的字段(因为 @Ignore 注释)。【参考方案8】:

您现在可以使用is 开始您的字段,但您不能在is 旁边有一个数字,例如:is2FooSelected,您必须重命名为isTwoFooSelected

【讨论】:

【参考方案9】:

在 Java 中出现此错误。

Java 中不能有以isis_ 开头的列。

尝试重命名该列。

另一种解决方案:

您要么必须在构造函数中传递字段并使用构造函数参数对其进行初始化,要么为其创建一个 setter。

例子:

public MyEntity(String name, ...) 
   this.name = name;
   ...


public void setName(String name) 
    this.name = name;

【讨论】:

【参考方案10】:

如果您希望您的实体可以使用val 不变性,这是可能的。

    您应该更新到 AndroidX 房间 current version。 检查相关的issue here,它被标记为Won't Fix 现在他们发布了与version 2.0.0-beta01 问题相关的修复程序 现在您可以将不可变的valdefault value 一起使用,例如:
@Entity("tbl_abc")
data class Abc(
    @PrimaryKey
    val id: Int = 0, 
    val isFavourite: Boolean = false
)

之前上面的sn-p会抛出Cannot find setter for field的错误。更改为 var 是一个很好的解决方法,但我更喜欢实体类不受外部调用的影响

【讨论】:

【参考方案11】:

如果有人在 2019 年遇到此线程,则只是一个更新,在花费数小时在线挖掘为什么它应该有效之后,但它没有。

如果您使用的是 AndroidX 版本 (androidx.room:room-<any>:2.*),则使用 val 可以按预期工作,但在使用旧版 android.arch.persistence.room:<any>:1.1.1 时却不能正常工作,而且似乎 2.* 版本没有在后一个 repo 上发布.

编辑:错别字

【讨论】:

是的,AndroidX 将替换支持库,所以 Room 永远不会是旧的 android.arch.persistence.room 上的 2.0。 感谢您的信息 val 在我的情况下对 Int 和 Long 不起作用,即使使用 androidx 和最新的 Room【参考方案12】:

根据https://***.com/a/46753804/2914140,如果你有一个自动生成的主键,你应该这样写:

@Entity(tableName = "story")
data class Story (
        val by: String,
        val descendants: Int,
        val score: Int,
        val time: Long,
        val title: String,
        val type: String,
        val url: String
)  
    @PrimaryKey(autoGenerate = true)
    var id: Int = 0

请注意,@PrimaryKey 写在类体内并包含修饰符 var

如果您以后想用不同的参数更新数据库中的一行,请使用这些行:

val newStory = story.copy(by = "new author", title = "new title") // Cannot use "id" in object cloning
newStory.id = story.id
dao.update(newStory)

更新

我还是不用AndroidX,Room是'android.arch.persistence.room:runtime:1.1.1'。

您可以从Serializable 扩展此类。但是如果你想从Parcelable扩展它,你会得到一个警告(超过id变量):Property would not be serialized inro a 'Parcel'. Add '@IgnoredOnParcel' annotation to remove this warning

然后我将 id 从主体移到构造函数。在 Kotlin 中,我使用 @Parcelize 创建 Parcelable 类:

@Parcelize
@Entity(tableName = "story")
data class Story (
    @PrimaryKey(autoGenerate = true)
    var id: Int = 0,

    val by: String,
    val descendants: Int,
    val score: Int,
    val time: Long,
    val title: String,
    val type: String,
    val url: String
) : Parcelable

【讨论】:

如果你这样做,你可能想要覆盖equalshashcode @Tenfour04,但为什么呢?它们在data class 中自动生成。但我没有比较两个对象。 它们只为构造函数属性生成。由于您将主键移到构造函数之外,id 不再包含在自动生成的 equalshashcode 中,这将是令人惊讶的行为。 @Tenfour04,同意你的看法,谢谢你的评论。【参考方案13】:

room db 库 java 代码生成存在问题。

我使用的是可选字段isFavorite。它给了我同样的错误然后我将我的字段名称更改为favorite 然后编译。

之前 var isFavorite: Int? = 0, 改变工作正常后 var favorite: Int? = 0, 谢谢

【讨论】:

更改您的数据类型。我的数据库中有相同的字段名为 isFavourite 我将字段类型更改为布尔值,现在它对我有用。 @ColumnInfo(name = "is_favourite") var isFavourite: Boolean = false【参考方案14】:

这是一个错误,已在 Room 2.1.0-alpha01 中修复

https://developer.android.com/jetpack/docs/release-notes#october_8_2018

错误修复

Room 现在将正确使用 Kotlin 的主要构造函数 避免将字段声明为变量的数据类。 b/105769985

【讨论】:

我正在使用2.1.0-alpha03,但我仍然收到此错误 在 2.1.0-alpha04 中相同【参考方案15】:

嘿,我不知道大家是否知道,但你不能有从is开始到Room的列。 例如你不能有这样的

   @Entity(tableName = "user")
   data class User (
        @PrimaryKey var id: Long? = null,
        var userName: String = "",
        var isConnectedToFB: Boolean = false,
)

【讨论】:

就我而言,“is”前缀不适用于字段名称。但“is”前缀适用于布尔类型字段。 Java 中也会发生同样的事情。您不能使用 "mIs___" 或 "is____" 。它会导致同样的错误。匈牙利符号不会救你。 在 kotlin 中工作 我也用这个答案解决了我的问题。我使用new 作为归档名称。然后将其更改为 newCall,它对我有用。谢谢! 没错。字段名称在布尔类型时不应以“is”开头。【参考方案16】:

另一个原因可能是字段的命名。如果您使用任何预定义的关键字,您将得到相同的错误。 例如,您不能将列命名为“is_active”。

参考:http://www.sqlite.org/lang_keywords.html

【讨论】:

以上是关于找不到字段的设置器 - 将 Kotlin 与 Room 数据库结合使用的主要内容,如果未能解决你的问题,请参考以下文章

Android Room 库错误:找不到字段的设置器。 (科特林)

错误:找不到字段的设置器 - 房间

@Embeddables 的 @ElementCollection 每个都具有 @Embeddable 字段导致“找不到设置器”异常

错误:找不到字段的设置器。 - java.util.ArrayList 中的大小 - Room 中的嵌入式 ArrayList 无法编译

Spring Boot + Kotlin + Gradle - 错误:在类中找不到主要方法

Kotlin 在片段中找不到按钮 ID,为啥?