7

my Question is how to store

List<Enum>

in RoomDatabase, so far I find no answer for that.

murt
  • 3,790
  • 4
  • 37
  • 48

5 Answers5

8

Enum:

public enum HelloEnum {
    A,
    B,
    C
}

Converter:

public class EnumConverter {

    @TypeConverter
    public List<HelloEnum> storedStringToEnum(String value) {
        List<String> dbValues = Arrays.asList(value.split("\\s*,\\s*"));
        List<HelloEnum> enums = new ArrayList<>();

        for (String s: dbValues)
            enums.add(HelloEnum.valueOf(s));

        return enums;
    }

    @TypeConverter
    public String languagesToStoredString(List<HelloEnum> cl) {
        String value = "";

        for (HelloEnum lang : cl)
            value += lang.name() + ",";

        return value;
    }    
}

In terms of inserting and fetching your data this will work not questions asked.

@Dao
public interface HelloPojoDao {

    @Query("SELECT * FROM helloPojo")
    List<HelloPojo> fetchAll();

    @Insert
    void insertPojo(HelloPojo pojo);
}

I would however point out that filtering by enum becomes a little more tricky now. For example if you want to write a query for fetching objects containing enum.A and enum.B, you will have to build a query that queries a string object for them. In this case "SELECT * FROM pojo WHERE enums contains ' A,' and ' B,'. As such it is better to assign number values to your enums (as @Kuffs answer details), as parsing ints will likely produce less issues than parsing strings.

Hope this resolves your issue. Feel free to ask any questions in the comment section, and happy hunting!

Jack Dalton
  • 3,536
  • 5
  • 23
  • 40
5

Ok using Kotlin is much simpler, when using name

class EnumTypeConverter {

    @TypeConverter
    fun restoreEnum(enumName: String): EnumType = EnumType.valueOf(enumName)

    @TypeConverter
    fun saveEnumToString(enumType: EnumType) = enumType.name
}
murt
  • 3,790
  • 4
  • 37
  • 48
1

Let me show the way of persisting/retrieving it using kotlin.

What restrictions do we have during the operation?

  • We store enum list as string field in database, so we can't have efficient select or search in database based on it
  • We have to serialize each enum value to string and deserialize it back
  • we can't base the behavior on ordinal field to prevent mistakes due to extending the enum in the future, so we need to have a constant field which represents each enum value
    • we can't base on default toString method of list to string, because it may depend on environment we exactly use

So proper implementation in my opinion will look like:

  1. Create Enumerable interface which all enum classes persisted to db should implement
  2. Each enum class should have creation method with passed serialized parameter to it which restores enum value
  3. Create kotlin extension to serialize/deserialize list of enums
  4. Create type converters for each type of enum

So, the Enumerable interface:

/** marks the enum with custom int code for each value */
interface Enumerable {
    val code: Int
}

enum example:

enum class PaymentSystemEntity constructor(
    override val code: Int
) : Enumerable {
    VISA(1),
    MASTERCARD(2),
    UNKNOWN(0);

    companion object {
        fun parseFromCode(code: Int): PaymentSystemEntity = values()
            .firstOrNull { it.code == code } ?: UNKNOWN
    }

}

serialization extensions:

fun <T> List<T>.asString(): String where T : Enum<T>, T : Enumerable = this
    .map { it.code }
    .joinToString(separator = ",")

fun <T> String.toEnumList(creator: (code: Int) -> T): List<T> where T : Enum<T>, T : Enumerable = this
    .split(',')
    .map { it.toInt() }
    .map { creator(it) }

Sample type converter for enum:

class PaymentSystemTypeConverter {

    @TypeConverter
    fun paymentSystemsToString(value: List<@JvmSuppressWildcards PaymentSystemEntity>): String =
         value.asString()

    @TypeConverter
    fun stringToPaymentSystems(value: String): List<@JvmSuppressWildcards PaymentSystemEntity> =
        value.toEnumList { PaymentSystemEntity.parseFromCode(it) }

}

You may have other types of collections implemented in a same way

Beloo
  • 9,723
  • 7
  • 40
  • 71
0

Serialise your enums into integers and store those instead.

When retrieving the values, perform the reverse operation.

Both operations are explained in my answer to this question:

How to match int to enum

Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • Ok, I think better to store as String "1" , "2", ... and so on, hence I can store more than one enum - good point. It requires boilerplate... hym is it necesarry? – murt Jun 22 '17 at 12:02
  • String or int. Doesn't really matter. The concept is the same. – Kuffs Jun 22 '17 at 12:13
0

Jack Dalton version in Kotlin:

@TypeConverter
fun storedStringListLineType(value: String): List<LineType> {
    val dbValues = value.split("\\s*,\\s*".toRegex()).dropLastWhile { it.isEmpty() }
    val enums: MutableList<LineType> = ArrayList()

    for (s in dbValues)
        enums.add(LineType.valueOf(s))
    return enums
}

@TypeConverter
fun listLineTypeToStoredString(listLineTypes: List<LineType>): String {
    var value = ""

    for (lineType in listLineTypes)
        value += lineType.name + ","

    return value
}
Damian JK
  • 479
  • 1
  • 3
  • 9