Reputation: 2761
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
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
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
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
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