Firebase: a clean way to use enum fields in Kotlin / Java?

My firebase data uses a lot of fields that have a string type, but really are enumeration values ​​(which I check in my validation rules). In order to load data into my Android application, according to the manual , the field must be the main String . I know that I can get around this with the second (excluded) field, which is an enumeration, and set this based on the string value. A brief 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 is not very clean:

  • The enum class is (1) long for enum, (2) the internals are repeated for each listing. And I have more of them.
  • It not only lists, then the created field above is really a timestamp, not Long .
  • Each model uses these enumeration fields many times, which inflates the model classes with repeatable code ...
  • Auxiliary field / functions become much worse / longer for fields with types such as Map<SomeEnum, Timestamp> ...

So, is there a way to do this right? Perhaps some library? Or somehow write a magic "field shell" that automatically converts strings to enumerations or numbers to timestamps, etc., but is still compatible with the Firebase library for receiving / setting data?

(Java solutions are also welcome :))

+6
source share
2 answers

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

To put it briefly, you can implement a delegate for String properties that does the conversion and actually gets / sets the value of another property that stores enum values ​​and then delegates 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 that you add the Kotlin reflection API, kotlin-reflect , as a dependency on your project. Using Gradle, use the compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" .

This will be explained below, but first let me add a convenience method to avoid instantiating 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 accessing a resource, delegate methods are called instead. And the delegate object, in turn, works with the weightUnitEnum property under the hood.

The added convenience function eliminates the need to write UserData::class.java on the property declaration site (using the reified type parameter) and provides conversion functions for EnumStringDelegate (you can create other functions with different transformations at any time or even create a function that receives functions conversions like lambdas).

Basically, this solution saves you from the code of the template, which represents a property of the enum type as a String property, taking into account the logic of conversion, and also allows you to get rid of excess code in your enum if you no longer use it.

Using this method, you can implement any other conversion between properties, for example, the number that you specified in the timestamp.

+4
source

I am in a similar situation and thus found your question, plus a number of other similar questions / answers.

I can’t answer your question directly, but this is what I ended up with: I decided to change my application, and not use enumeration data types at all - mainly because of the recommendations of the Google dev portal, which shows how poorly enumerations are presented. Watch the video below https://www.youtube.com/watch?v=Hzs6OBcvNQE

+1
source

Source: https://habr.com/ru/post/1014083/


All Articles