ObjectBox 新增字段与 Kotlin non-nullable 冲突的解决方案
问题描述
我有一个 Android APP 使用 ObjectBox 作为数据库, 并使用纯 Kotlin 语言开发.
今天我想给数据库的一个 Model 添加一个新字段. 于是我就按照惯例的方法:
- 在 Model class 中添加字段
- make 一下工程, 确保 ObjectBox 的 Gradle 插件创建好自动生成的类, 并更新 default.json
- 编译安装到手机中
装到手机上运行时遇到了 Crash, 错误信息如下:
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter foo
问题原因
出现这个问题的原因是:
- 手机上已有的数据库中的 model 还是老的 model
- 在从数据库中取这个 model 的数据时, 会自动对 foo 这个属性设一个 null
- 由于这个 model 是一个 Kotlin 类, foo 是 non-nullable 的
- 这就导致在运行时触发了 Kotlin 的 non-nullable 非法异常, 一个不该 null 的字段 null 了
问题修复
我使用以下方法修复了这个问题:
首先, 先把这个新增字段设为 nullable 的:
var foo: String? = ""
并 make 一下工程.
之后修复的策略是, 在程序刚运行的时机 (尚未开展数据库操作) 补一个数据库操作:
- 查找已有数据库中当前字段为 null 的 (老的数据都命中了)
- 之后依次给它这个字段赋一个初值并保存
代码如下:
val data = someBox(applicationContext).query().isNull(Project_.foo).build().find()
data.forEach { p ->
p.foo = ""
someBox(applicationContext).put(p)
}
再次编译运行, 没有报错顺利进入程序, 程序已经将数据库中的老数据都更新了.
之后再把这个 model 新增字段改回 non-nullable 的:
var foo: String = ""
再次编译运行, 由于数据库已经更新了, 因此顺利运行.
2018-1-7 更新: 就使用 nullable 可以吗
在前面的解决方法中, 我先把新增字段 nullable 改成 non-nullable, 修复了之后又改回了 nullable.
我现在有一个问题, 不改回来会有问题吗? 就用 nullable 做字段可以吗?
我又试了一下, 将新增字段为 nullable 而不改回来.
发现这样是可以的, 老的数据由于没有这个字段, 这个字段的值就为 null.
新的数据有了这个字段, 这个字段的值就有值.
看来在 Kotlin 中使用 ObjectBox, model 的字段使用 nullable 更好一些.
总结
修复这个问题我是通过两个步骤编译两次才把问题修复了. 这对于线上的产品是很难的.
首先, 这个问题很难用 Hotpatch 修复:
- 首先 ObjectBox 会根据 Model 自动生成一些类, 我们在 patch 的时候, 需要找到这些自动生成的类, 一并 patch 掉.
- 同时由于 Model 是使用 Kotlin 编写的, Kotlin 代码如何打 patch 呢? 这也是个值得研究一下的问题.
想来想去, 线上解决方案只能清掉数据库了.
我对这件事情的思考是:
- ObjectBox 是一个比较新的项目, 虽然优点比较突出, 但是还不完善, 还有一些小问题
- Kotlin 同时又是一门比很新 (跟 Java 相比) 的语言, ObjectBox 虽然支持 Kotlin, 但是用户群体比 Java 小得多, 很多深入的问题没有暴露出来
- 今天我遇到的这个问题, 其实 ObjectBox 社区中已经有人提了, 项目的维护者给出了一个简单的 "解决" 方法, 但不幸的是这个方法本身是错误的
- 这就说明, 在新问题上, 即使是大牛, 精力也是有限的, 也会掉坑里犯错误, 不像在他熟悉的领域中那般呼风唤雨
- 对于商业项目, 在技术选型中一定要慎重, 不要一味求新求异, 尝试是可以的, 但是要控制风险, 不能拿核心产品冒这个险
这就是我对今天遇到的这个问题的总结, 希望对大家也能有所帮助.
欢迎通过点赞对我提供支持, 你的支持将是我持续写作的动力.