Firebase:在Kotlin / Java中使用枚举字段的干净方式? [英] Firebase: clean way for using enum fields in Kotlin/Java?
问题描述
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> enumString
类型就足够了,这可以通过使用 Kotlin委托属性。
简而言之,您可以为执行转换的
String
属性实现一个委托,并实际获取/设置存储enum
值的另一个属性的值,然后将String
属性委托给它。
一个可能的实现如下所示:
b $ b
$ 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)
}
}
$ p $注意:此代码需要您添加Kotlin反射API, .kotlin / kotlin-reflectrel =nofollow noreferrer>kotlin-reflect
,作为项目的依赖项。使用Gradle,使用compileorg.jetbrains.kotlin:kotlin-reflect:$ kotlin_version
。这将在下面解释,但首先让我添加一个方便的方法来避免直接创建实例:
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 aLong
.- 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 ofString
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 theenum
values, and then delegate theString
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, usecompile "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 theString
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 theweightUnitEnum
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 toEnumStringDelegate
(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 aString
property, given the conversion logic, and also allows you to get rid of the redundant code in yourenum
, 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屋!