【问题标题】:How to gracefully support data type change on firebase database?如何优雅地支持 firebase 数据库上的数据类型更改?
【发布时间】:2019-03-05 03:27:19
【问题描述】:

使用 Firebase 实时数据库快速启动并运行项目非常棒。

但是当项目运行时,有时,稍后,您需要更新自定义对象模型的数据类型,事情可能会变得不那么令人敬畏。

假设,我使用以下模型(kotlin 示例)启动项目:

@Parcelize
data class CustomModel (
    var customField: Int = 0,
)

customField 表示布尔值 0 或 1,但作为数字

几周后,我意识到这个字段可能应该是布尔类型的字段(firebase 原生支持)

所以我想把上面的模型改成:

@Parcelize
data class CustomModel (
    var customField: Boolean = false,
)

问题是,我已经为我的customField (Int) 添加了一些旧格式的数据到我的文档存储中

并使用 firebase DataSnapshot 的方法:

@PublicApi
public <T> T getValue(@NonNull Class<T> valueType)

导致com.google.firebase.database.DatabaseException: Failed to convert value of type java.lang.Int to Boolean

所以问题很简单,我们如何在 Firebase 中处理数据库架构迁移?

我知道我可以捕获异常,但我需要真正添加一些自定义转换逻辑,以便将这些 Int 转换为布尔值,反之亦然,以便与旧客户端向后兼容

【问题讨论】:

    标签: java android firebase firebase-realtime-database kotlin


    【解决方案1】:

    我能想到的最简单的解决方案是更改属性的数据类型。根据您的逻辑,这可以通过查询数据库以获取该特定属性的所有值并将其存储为布尔值来完成。如果值为0,则存储false,如果值为1,则存储true。获得值后,只需删除该属性并使用正确的(布尔)数据类型再次添加它。

    编辑:如果您的应用已发布,您是对的,您不能一步完成。在这种情况下,您应该仅在用户打开应用程序时进行更改。什么是流量?您使用以下机制进行更新:

    为您的应用创建一个新版本,您可以在其中选择转换属性的数据类型。用户读取该子项后,将值作为Int 获取,将其存储在布尔变量中,删除该属性并再次正确添加。

    这样,使用旧版应用的用户不会受到影响,因为他们使用的是Int属性,而使用新版应用的用户将正确使用数据。

    【讨论】:

    • 你好 Alex,如果我这样做,我将失去与在同一数据库上运行的旧客户端的向后兼容性
    • 这实际上是我想要做的,我希望,我可以以某种方式将我的自定义逻辑添加到快照 getValue 方法中。但找不到办法做到这一点
    • 据我所知没有一个,因为getValue 只获取数据。它不做任何其他操作。
    • 谢谢亚历克斯!如果有人提出了一种创新的方法,我将暂时保留这个问题
    • 对不起,亚历克斯,但如果我不接受你的回答,这意味着它并没有真正帮助我。我正在寻找一种从 Firebase 进行自定义反序列化的方法。正如我告诉你的那样,如果有人提出一种围绕 getValue 的插件自定义逻辑的创新方法,我将把问题留待解决。
    【解决方案2】:

    所以我终于想出了一个干净的解决方案来解决这个数据类型迁移问题。 我能够利用 kotlin property delegate 来更改我的数据类字段的类型。

    首先,我必须将我的属性类型更改为我想要迁移到的数据类型(在我的例子中从 Int 到 Boolean),并添加一个自定义属性委托:

    @Parcelize
    data class CustomModel (
        private var _customField: Boolean = false,
    ): Parcelable {
        var customField: Any by FirebaseBooleanDelegate(_customField)
    }
    

    这里属性委托的作用是:代理对我们上面更改的现在私有属性_customField的读写访问。 我们也可以使用自定义的 getter 和 setter。

    代码如下:

    class FirebaseBooleanDelegate(var isOn: Boolean) : ReadWriteProperty<Any?, Any> 
    {
    
        override fun getValue(thisRef: Any?, property: KProperty<*>): Any {
            return isOn
        }
    
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Any) {
            Log.d("FirebaseBooleanDelegate", "calling setter of ${property.name} with value $value")
            isOn = helperSet(value)
        }
    }
    
    private fun <T> helperSet(t: T) = when (t) {
        is Number -> t.toBoolean()
        is Boolean -> t
        else -> throw IllegalArgumentException()
    }
    

    在 helperSet 方法中,我有处理从 Number 到 Boolean 的数据类型迁移的逻辑。

    最后,因为我的数据类属性 _customField 现在是私有的,所以我必须为我的 @Parcelized 类编写一个自定义 parceler,否则我的新私有字段将不会被序列化回 firebase:

    private companion object : Parceler<CustomModel> {
        override fun CustomModel.write(parcel: Parcel, flags: Int) {
            parcel.writeByte((if (customField as Boolean) 1 else 0).toByte())
        }
    
        override fun create(parcel: Parcel): CustomModel {
            return CustomModel(
                    _customField = parcel.readByte() != 0.toByte()
            )
        }
    }
    

    通过所有这些设置,Datasnapshot's getValue 方法现在能够正确处理数据类型迁移而不会崩溃。

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 2012-05-26
      • 1970-01-01
      • 2015-11-27
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 2021-11-18
      • 2023-04-09
      相关资源
      最近更新 更多