Firebase: чистый способ использования полей enum в Kotlin / Java?

Мои данные о firebase используют много полей, которые имеют тип строки, но действительно являются значениями перечисления (которые я проверяю в моих правилах проверки). Чтобы загрузить данные в мое приложение для Android, следуя руководству , поле должно быть основным 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-решения тоже приветствуются :))

Если преобразование между свойством с enum и другим свойством типа String достаточно, это можно легко сделать гибким способом, используя делегированные свойства Kotlin .

Короче говоря, вы можете реализовать делегат для свойств 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) } } 

Примечание. Этот код требует, чтобы вы добавляли API отражения Kotlin, 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) , вы делегируете свойство var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum) делегата. Это означает, что при доступе к ресурсу вместо этого вызываются методы делегата. И объект делегата, в свою очередь, работает с собственностью weightUnitEnum под капотом.

Добавленная функция удобства избавляет вас от необходимости писать UserData::class.java на сайте объявления свойств (используя параметр типа EnumStringDelegate ) и предоставляет функции преобразования EnumStringDelegate (вы можете в любое время создавать другие функции с различными преобразованиями, или даже сделать функцию, которая получает функции преобразования как lambdas).

В основном, это решение избавляет вас от кода шаблона, который представляет свойство типа enum как свойство String , учитывая логику преобразования, а также позволяет вам избавиться от избыточного кода в вашем enum , если вы его нигде не используете остальное.

Используя этот метод, вы можете реализовать любое другое преобразование между свойствами, например, число, которое вы указали по времени .

Я в подобной ситуации, поэтому нашел ваш вопрос, плюс целый ряд других подобных вопросов / ответов.

Не могу ответить на ваш вопрос напрямую, но это то, что я закончил: я решил изменить свое приложение и не использовать типы данных enum вообще – в основном из-за совета с портала Google dev, который показывает, насколько плохие перечисления на производительности приложения. Смотрите видео ниже https://www.youtube.com/watch?v=Hzs6OBcvNQE