Ошибка Android Room: TypeConverter не распознается для списка перечислений

Библиотека Room не распознает TypeConverter, который я создал для List перечислений. Однако, когда я меняю его на ArrayList перечислений, он работает нормально. Кто-нибудь знает, почему и что я могу сделать, чтобы эта работа работала с List? (Использовать List в Kotlin проще, и я действительно не хочу конвертировать назад и вперед в ArrayList только из-за этого).

Вот мой код:

Моя модель:

@Entity
data class Example(@PrimaryKey val id: String?,
                   val name: String,
                   var days: List<DayOfWeek>?)

DayOfWeek - это перечисление:

enum class DayOfWeek {

    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;

    val value: Int
        get() = ordinal + 1


    companion object {

        private val ENUMS = DayOfWeek.values()

        fun of(dayOfWeek: Int): DayOfWeek {
            if (dayOfWeek < 1 || dayOfWeek > 7) {
                throw RuntimeException("Invalid value for DayOfWeek: " + dayOfWeek)
            }

            return ENUMS[dayOfWeek - 1]
        }

    }

}

My TypeConverter:

private const val SEPARATOR = ","

class DayOfWeekConverter {

    @TypeConverter
    fun daysOfWeekToString(daysOfWeek: List<DayOfWeek>?): String? {
        return daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)
    }

    @TypeConverter
    fun stringToDaysOfWeek(daysOfWeek: String?): List<DayOfWeek>? {
        return daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }
    }

}

И я установил его в своем классе БД следующим образом:

@Database(entities = arrayOf(Example::class), version = 1)
@TypeConverters(DayOfWeekConverter::class)
abstract class AppDatabase : RoomDatabase() {

    abstract fun exampleDao(): ExampleDao

}

Мой DAO выглядит так:

@Dao
interface ExampleDao {

    @Query("SELECT * FROM example")
    fun getAll(): LiveData<List<Example>>

    @Insert(onConflict = REPLACE)
    fun save(examples: List<Example>)

}

Ошибка, которую я получаю с этим кодом:

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
e: 

e:     private java.util.List<? extends com.example.DayOfWeek> days;

Как я сказал выше, если я изменю свойство days на ArrayList<DayOfWeek> (и внесу изменения в ArrayList в DayOfWeekConverter), тогда все будет работать нормально. Если бы кто-нибудь мог помочь мне разобраться в этом и рассказать, как я могу использовать List здесь, это было бы большим подспорьем, это сводит меня с ума: /.


person Franco    schedule 17.10.2017    source источник
comment
Опубликуйте, пожалуйста, свой класс "ExampleDao".   -  person Pravin Divraniya    schedule 26.10.2017
comment
DAO кажется неуместным для проблемы, с которой я столкнулся с @PravinDivraniya, но я все равно добавил его сейчас на случай, если он поможет вам разобраться в проблеме. Ваше здоровье.   -  person Franco    schedule 27.10.2017
comment
Я столкнулся с той же проблемой. Учитывая, что, очевидно, это невозможно, я создал запрос функции: Issueetracker.google.com/issues/69164099   -  person Pedro Loureiro    schedule 10.11.2017
comment
@Franco, я обновил свое решение для компиляции с Room   -  person Tomek Polański    schedule 05.01.2018
comment
Спасибо @ TomekPolański, я постараюсь протестировать его, когда у меня будет время, и я приму ваш ответ, если я смогу убедиться, что он работает должным образом.   -  person Franco    schedule 08.01.2018


Ответы (4)


Почему-то Room не нравится Kotlin List, но когда я заменил List на MutableList, он заработал:

@Entity
data class Example(@PrimaryKey val id: String,
                   val name: String,
                   var days: MutableList<DayOfWeek>?)

class DayOfWeekConverter {
    companion object {

        @TypeConverter
        @JvmStatic
        fun daysOfWeekToString(daysOfWeek: MutableList<DayOfWeek>?): String? =
                daysOfWeek?.map { it.value }?.joinToString(separator = SEPARATOR)

        @TypeConverter
        @JvmStatic
        fun stringToDaysOfWeek(daysOfWeek: String?): MutableList<DayOfWeek>? =
                daysOfWeek?.split(SEPARATOR)?.map { DayOfWeek.of(it.toInt()) }?.toMutableList()
    }
}

Это не идеальное решение, но надеюсь, что вы сможете изучить его подробнее.

Также вам нужно изменить @PrimaryKey, чтобы он не допускал значения NULL

person Tomek Polański    schedule 04.01.2018
comment
Кажется, я помню, что пробовал, и это не сработало, но я не уверен, как это было несколько месяцев назад. Если у меня будет время, я могу попробовать, но я очень занят, так что я не смогу это сделать какое-то время. Вы сами это пробовали? - person Franco; 05.01.2018
comment
Наконец-то у меня появилось время попробовать это, и это действительно сработало с MutableList :), спасибо @ TomekPolański! Думая об этом, я думаю, это имеет смысл, поскольку List в Kotlin неизменяемы, и, следовательно, вы не можете добавлять к ним значения, что, возможно, именно то, что Room пытается сделать в фоновом режиме. - person Franco; 11.01.2018
comment
Просто хотел добавить, что вы также должны добавить аннотацию @Converters в КОМНАТНУЮ БАЗУ ДАННЫХ. Я этого не делал, и это продолжало давать мне ошибки. См. Также adrianhall.github.io / android / 2018/08/08 /. - person The Fox; 17.10.2019

Полная сигнатура List в Kotlin - _2 _ (_ 3_ в Java), нет смысла преобразовывать такой универсальный тип. Другими словами, Room не знает, является ли вход DayOfWeek или его подклассом.

Что касается ArrayList и MutableList, их полная подпись - ArrayList<E> и MutableList<E> соответственно, тип ввода фиксирован, и, следовательно, Room знает, как его преобразовать.

person jaychang0917    schedule 17.08.2018

У нас нет возможности сохранить и получить перечисление List без списка массивов. Номер не поддерживает. Но если вы хотите избежать использования списка массивов, вы можете создать объект ListDayOfWeek с атрибутом List. Я попробовал и все нормально. Если вам нужен код, ответьте здесь. Я выложу это.

person Nguyễn Gia Lễ    schedule 24.10.2017
comment
Я уже знал об этом обходном пути, но считаю его неприемлемым, поскольку он не масштабируется. Спасибо, в любом случае. - person Franco; 24.10.2017

Вы не должны хранить это в своей базе данных в таком виде. Лучше построить что-то подобное и сохранить как int:

enum class DaysOfWeek(var bitValue: Int) {
    Monday(64),
    Tuesday(32),
    Wednesday(16),
    Thursday(8),
    Friday(4),
    Saturday(2),
    Sunday(1);
}

Наконец, ваши служебные функции для преобразования из / в enum.

fun daysToBitValue(days: EnumSet<DaysOfWeek>): Int {
        var daysBitValue = 0
        for (`val` in days) daysBitValue += `val`.bitValue
        return daysBitValue
}


private fun fromBitValues(origBitMask: Int): EnumSet<DaysOfWeek> {

        val ret_val = EnumSet.noneOf(DaysOfWeek::class.java)

        var bitMask = origBitMask

        for (`val` in DaysOfWeek.values()) {
            if (`val`.bitValue and bitMask == `val`.bitValue) {
                bitMask = bitMask and `val`.bitValue.inv()
                ret_val.add(`val`)
            }
        }

        if (bitMask != 0) {
            throw IllegalArgumentException(String.format(Locale.getDefault(), "Bit mask value 0x%X(%d) has unsupported bits 0x%X.  Extracted values: %s", origBitMask, origBitMask, bitMask, ret_val))
        }

        return ret_val
  }

Теперь вы можете сохранить int и получить дни недели позже:

@Entity
data class Example(@PrimaryKey val id: String?,
                   val name: String,
                   var days: Int = 0) 

или вы используете перечисление и напишите для этого подходящий преобразователь типов.

@Entity
data class Example(@PrimaryKey val id: String?,
                   val name: String,
                   var days: EnumSet<DaysOfWeek>?) 


class DayOfWeekConverter {

    @TypeConverter
    fun daysOfWeekToString(daysOfWeek: EnumSet<DayOfWeek>?) =
        daysOfWeek?.let{ YourUtilities.daysToBitValue(it) }

    @TypeConverter
    fun intToDaysOfWeek(daysOfWeek: Int?) = 
        daysOfWeek?.let {  YourUtilities.fromBitValues(it) } 
}

Вы можете просмотреть перечисление и получить все дни, используя

for (aDaysOfWeekEnumSet in daysOfWeekEnumSet) 
             info{ "day = ${aDaysOfWeekEnumSet.name"} 

Код не протестирован, потому что я не на моем компьютере, но это должно показать концепцию, которая работает лучше, чем сохранение перечисления (которое является просто псевдонимом для предопределенного значения!)

person Emanuel S    schedule 24.10.2017
comment
Думаю, вы не читали мой код выше, я уже пытаюсь сохранить его как список целых чисел, а не перечислений, поэтому ваш ответ не касается моей проблемы. - person Franco; 24.10.2017
comment
Вот почему я написал рабочий пример. Вам не нужен список, если вы решите его поразрядно. Попытайтесь понять мой Кодекс. Это правильный способ хранения дней недели. - person Emanuel S; 24.10.2017
comment
Я думаю, что я не был ясен в моем предыдущем комментарии, я уже сохраняю это как String, который состоит из списка объединенных целых чисел, не пытаясь сохранить сам список, как вы, кажется, думаете, это все в моем коде выше . Я прочитал ваш код, но не уверен, почему побитовое решение - это правильный способ хранения рабочих дней, я не думаю, что это правильный способ, это просто еще один обходной путь, отличный от того, который я уже использовал. В конце концов, я хочу не еще один обходной путь, а понять, почему это не работает с List и есть ли способ использовать его без добавления дополнительного кода. - person Franco; 24.10.2017