Franco
Franco

Reputation: 2761

Android Room error: TypeConverter not recognised for List of Enums

The Room library is not recognizing a TypeConverter I created for a List of enums. However, when I change this to an ArrayList of enums it works fine. Anyone has any idea why and what can I do to make this work with List? (Using List in Kotlin is easier and I really don't wanna be converting back and forwards to ArrayList just because of this).

Here is my code:

My model:

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

DayOfWeek is an enum:

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

}

And I set it in my DB class like this:

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

    abstract fun exampleDao(): ExampleDao

}

My DAO looks like this:

@Dao
interface ExampleDao {

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

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

}

The error I get with this code is:

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;

Like I said above, if I change the days property to ArrayList<DayOfWeek> (and make the changes to ArrayList in DayOfWeekConverter) then everything works fine. If anyone can help me figure this out and tell me how I can use List here it'd be of great help, it is driving me crazy :/.

Upvotes: 17

Views: 11797

Answers (4)

jaychang0917
jaychang0917

Reputation: 1888

The full signature of List in Kotlin is List<out E>(List<? extend E> in Java), it doesn't make any sense to convert such generic type. In other words, Room doesn't know if the input is a DayOfWeek or its subclass.

As for ArrayList and MutableList, their full signature is ArrayList<E> and MutableList<E> correspondingly, the input type is fixed and hence Room knows how to convert it.

Upvotes: 4

Tomek Polański
Tomek Polański

Reputation: 1753

For some reason, Room does not like Kotlin List, but when I've replaced List with MutableList it started to work:

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

This is not perfect solution, but hope you can investigate more with that.

Also you need to change @PrimaryKey to be not nullable

Upvotes: 21

Emanuel
Emanuel

Reputation: 8106

You should not store it like that into your database. Better build something like that and store it as int:

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

Finally your utility functions to convert from/to 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
  }

Now you can either store an int and get the weekdays later:

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

or you use the enum and write a proper typeconverter for that.

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

You can loop through the enum and get all days by using

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

Code is untested because im not on my Computer, but this should show the concept which works better then storing an enum (which is just an alias to a predefined value!)

Upvotes: -2

Nguyễn Gia Lễ
Nguyễn Gia Lễ

Reputation: 357

We have no way to store and get a List enum without array list. Room does not support it. But if you want to avoid using array list, you can create an object ListDayOfWeek with List is an attribute. I tried it and it's ok. If you need code, please reply here. I will post it.

Upvotes: -1

Related Questions