Firebase:在 Kotlin/Java 中使用枚举字段的干净方式?

Posted

技术标签:

【中文标题】Firebase:在 Kotlin/Java 中使用枚举字段的干净方式?【英文标题】:Firebase: clean way for using enum fields in Kotlin/Java? 【发布时间】:2017-05-29 19:44:54 【问题描述】:

我在 firebase 上的数据使用了许多具有字符串类型的字段,但实际上是枚举值(我检查了我的验证规则)。要将数据下载到我的 android 应用程序following the guide,该字段必须是基本的String。我知道我可以使用作为枚举的第二个(排除的)字段来解决这个问题,并根据字符串值进行设置。一个简短的例子:

class UserData : BaseModel() 
    val email: String? = null
    val id: String = ""
    val created: Long = 0
    // ... more fields omitted for clarity
    @Exclude
    var weightUnitEnum: WeightUnit = WeightUnit.KG
    var weightUnit: String
        get() = weightUnitEnum.toString()
        set(value)  weightUnitEnum = WeightUnit.fromString(value) 


enum class WeightUnit(val str: String) 
    KG("kg"), LB("lb");
    override fun toString(): String = str
    companion object 
        @JvmStatic
        fun fromString(s: String): WeightUnit = WeightUnit.valueOf(s.toUpperCase())
    

现在,虽然这可行,但它并不是很干净:

enum class 本身 (1) 有点长 枚举,(2)每个枚举都重复内部。而且我还有更多。 不只是枚举,上面的created字段真的是一个时间戳, 不是Long。 每个模型都多次使用这些枚举字段,这会使模型类的代码可重复... 对于类型为 Map<SomeEnum, Timestamp>...的字段,辅助字段/函数变得更糟/更长了...

那么,有什么方法可以正确地做到这一点吗?也许是某个图书馆?或者以某种方式编写一个神奇的“字段包装器”,它可以自动将字符串转换为枚举,或将数字转换为时间戳,等等,但仍与用于获取/设置数据的 Firebase 库兼容?

(也欢迎 Java 解决方案 :))

【问题讨论】:

【参考方案1】:

如果您的enum 值的属性与String 类型的另一个属性之间的转换就足够了,则可以使用Kotlin delegated properties 以灵活的方式轻松完成。

简而言之,您可以为String 属性实现一个委托,它执行转换并实际获取/设置存储enum 值的另一个属性的值,然后将String 属性委托给它。

一种可能的实现如下所示:

class EnumStringDelegate<T : Enum<T>>(
        private val enumClass: Class<T>,
        private val otherProperty: KMutableProperty<T>,
        private val enumNameToString: (String) -> String,
        private val stringToEnumName: (String) -> String) 

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String 
        return enumNameToString(otherProperty.call(thisRef).toString())
    

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) 
        val enumValue = java.lang.Enum.valueOf(enumClass, stringToEnumName(value))
        otherProperty.setter.call(thisRef, enumValue)
    

注意:此代码要求您将 Kotlin 反射 API kotlin-reflect 添加为项目的依赖项。对于 Gradle,请使用 compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

这将在下面解释,但首先让我添加一个方便的方法以避免直接创建实例:

inline fun <reified T : Enum<T>> enumStringLowerCase(
    property: KMutableProperty<T>) = EnumStringDelegate(
    T::class.java,
    property,
    String::toLowerCase,
    String::toUpperCase)

还有一个你的类的用法示例:

// if you don't need the `str` anywhere else, the enum class can be shortened to this:
enum class WeightUnit  KG, LB  

class UserData : BaseModel() 
    // ... more fields omitted for clarity
    @Exclude
    var weightUnitEnum: WeightUnit = WeightUnit.KG
    var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum)

现在,解释:

当您编写var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum) 时,您将String 属性委托给构造的委托对象。这意味着当访问属性时,会调用委托方法。反过来,委托对象在后台使用 weightUnitEnum 属性。

我添加的便利函数使您不必在属性声明站点编写UserData::class.java(使用reified type parameter)并提供转换函数到EnumStringDelegate(您可以随时创建具有不同转换的其他函数时间,或者甚至制作一个接收转换函数作为 lambdas 的函数)。

基本上,这个解决方案将您从将enum 类型的属性表示为String 属性的样板代码中保存下来,给定转换逻辑,并且还允许您摆脱enum 中的冗余代码,如果你不在其他地方使用它。

使用这种技术,您可以实现属性之间的任何其他转换,例如您提到的数字到时间戳

【讨论】:

感谢您的精彩解释 :) 我已经根据您的建议实现了一些东西,但最后似乎 Firebase 库在从/向数据库获取/保存对象时跳过了委托属性。你可能知道如何解决这个问题吗?详情:***.com/questions/42737499/… @quezak 尝试使用@PropertyName:***.com/questions/38681260/… 我没有任何使用 Firebase 的经验,但它似乎只适用于默认情况下的字段。【参考方案2】:

我处于类似情况,因此找到了您的问题,以及许多其他类似的问题/答案。

无法直接回答您的问题,但这就是我最终要做的事情:我决定更改我的应用程序并且根本不使用枚举数据类型 - 主要是因为 Google 开发门户网站的建议显示了应用程序上的枚举有多糟糕表现。见下方视频https://www.youtube.com/watch?v=Hzs6OBcvNQE

【讨论】:

以上是关于Firebase:在 Kotlin/Java 中使用枚举字段的干净方式?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Cloud Functions for Firebase 中使 HTTP 请求异步/等待?

Firebase云Firebase。如何在本地模式下在项目间移动数据

ReferenceError:进程未在 firebase-messaging-sw.js 中定义

在除活动之外的另一个类中使用 Firebase 需要重复调​​用

Kotlin 与 Java 相互调用

无法在 Kotlin/Java 中使用 jooq DSL 执行 where 子句