Firebase:在Kotlin / Java中使用枚举字段的干净方式? [英] Firebase: clean way for using enum fields in Kotlin/Java?

查看:274
本文介绍了Firebase:在Kotlin / Java中使用枚举字段的干净方式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在firebase上的数据使用了很多字段类型的字段,但实际上是枚举值(我检查了我的验证规则)。要将数据下载到我的Android应用中,请按照指南,该字段必须是基本的 String 。我知道我可以解决这个第二个(排除)字段这是一个枚举,并根据字符串值进行设置。一个简单的例子:

  class UserData:BaseModel(){
val email:String? = null
val id:String =
val已创建:Long = 0
// ...为了清晰起见省略了更多字段
@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);
重写fun toString():String = str
伴随对象{
@JvmStatic
fun fromString(s:String):WeightUnit = WeightUnit.valueOf(s.toUpperCase())






$现在,虽然这样做,它并不是很干净:
$ b

  • 对于$ b $, enum class 本身是(1) b枚举,(2)每个枚举重复内部。而且我还有更多。
  • 不仅仅是枚举,上面的创建的字段实际上是一个时间戳,
    不是一个 Long

  • 每个模型都使用这些枚举字段很多次, / li>
  • 对于类型为 Map< SomeEnum,Timestamp> ...的字段,帮助程序字段/ / li>


那么,有没有办法正确地做到这一点?有些图书馆可能?或者某种方式来编写一个神奇的字段包装,它会自动将字符串转换为枚举或数字到时间戳记等等,但仍然与Firebase库兼容获取/设置数据?

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

c $ c> enum
值,另一个属性 String 类型就足够了,这可以通过使用
Kotlin委托属性



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



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

$ p $ class $ EnumStringDelegate< ;>(
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 this(thisRef).toString())
}

operator fun setValue(thisRef:Any?,property:KProperty< *> ;, value:String){
enumValue = java.lang.Enum .valueOf(enumClass,stringToEnumName(value))
otherProperty.setter.call(thisRef,enumValue)
}
}
.kotlin / kotlin-reflectrel =nofollow noreferrer> kotlin-reflect ,作为项目的依赖项。使用Gradle,使用 compileorg.jetbrains.kotlin:kotlin-reflect:$ kotlin_version

b $ b

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

  inline fun  T :: class.java,
property,
String :: toLowerCase,
String :: toUpperCase )

以及您的课程的使用示例:

  //如果在其他地方不需要`str`,则enum类可以缩写为:
enum class WeightUnit {KG,LB}
$ b $ class UserData:BaseModel(){
// ...为了清晰起见省略了更多的字段
@Exclude
var weightUnitEnum:WeightUnit = WeightUnit.KG
var weightUnit:String by enumStringLowerCase(UserData :: weightUnitEnum)
}

现在解释:当你通过enumStringLowerCase(UserData :: weightUnitEnum)编写 var weightUnit:String时,委托 String 属性到构造的委托对象。这意味着当访问属性时,调用委托方法。然后,委托对象依次使用引擎盖下的 weightUnitEnum 属性。

我添加的便利函数使您不必在属性声明中编写 UserData :: class.java 网站(使用指定类型参数),并提供转换函数为 EnumStringDelegate (您可以随时创建具有不同转换的其他函数,甚至可以创建一个将转换函数接收为lambdas的函数)。 b
$ b

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

使用这种技术,您可以在属性之间实现任何其他转换,例如您提到的 number到timestamp

My data on firebase uses many fields which have string type, but really are enum values (which I check in my validation rules). To download the data into my Android app, following the guide, the field must be a basic String. I know I can work around this with a second (excluded) field which is an enum, and set this basing on the string value. A short example:

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())
    }
}

Now, while this works, it's not really clean:

  • The enum class itself is (1) kinda long for an enum, (2) the insides are repeated for every enum. And I have more of them.
  • It's not only enums, the created field above is really a timestamp, not a Long.
  • Each model uses these enum fields a lot of times, which bloats the model classes with repeatable code...
  • The helper field/functions are getting much worse/longer for fields with types such as Map<SomeEnum, Timestamp>...

So, is there any way to do this properly? Some library maybe? Or some way to write a magic "field wrapper" that would automatically convert strings to enums, or numbers to timestamps, and so on, but is still compatible with Firebase library for getting/setting data?

(Java solutions are welcome too :) )

解决方案

If the conversion between a property with your enum value and another property of String type is enough, this can be easily done in a flexible way using Kotlin delegated properties.

To say it short, you can implement a delegate for String properties which performs the conversion and actually gets/sets the value of another property storing the enum values, and then delegate the String property to it.

One possible implementation would look like this:

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

Note: This code requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. With Gradle, use compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version".

This will be explained below, but first let me add a convenience method to avoid creating the instances directly:

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

And a usage example for your class:

// 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)
}

Now, the explanation:

When you write var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum), you delegate the String property to the constructed delegate object. This means that when the property is accessed, the delegate methods are called instead. And the delegate object, in turn, works with the weightUnitEnum property under the hood.

The convenience function I added saves you from the necessity of writing UserData::class.java at the property declaration site (using a reified type parameter) and provides the conversion functions to EnumStringDelegate (you can create other functions with different conversions at any time, or even make a function that receives the conversion functions as lambdas).

Basically, this solution saves you from the boilerplate code that represents a property of enum type as a String property, given the conversion logic, and also allows you to get rid of the redundant code in your enum, if you don't use it anywhere else.

Using this technique, you can implement any other conversion between properties, like the number to timestamp you mentioned.

这篇关于Firebase:在Kotlin / Java中使用枚举字段的干净方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆