Search code examples
androidkotlintype-conversiongsonandroid-room

Error using TypeConverter (GSON) for Room with custom objects in Kotlin


I am receiving the error Query method parameters should either be a type that can be converted into a database column or a List / Array that contains such type. You can consider adding a Type Adapter for this. for "settings".

I don't know what I'm doing wrong, since I have a conversor for Settings. In Converters, "fromScore", "toScore", "fromSettings" and "toSettings" are being used. The rest of the methods are greyed out.

Also feel free to suggest other (possibly better) ways than Room to store this data, please. Thanks.

The entity:

@Entity(tableName = "user_table")
data class User(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id") val id: Int = 0,
    @ColumnInfo(name = "score") val score: Score = Score(),
    @ColumnInfo(name = "settings") val settings: List<Settings> = emptyList(),
) {

    data class Score(
        val score: Int = 0,
        val correctAnswers: Int = 0,
    )

    data class Settings(
        val name: String = "",
        val mode: Set<GameMode> = emptySet(),
        val selectionMode: Boolean = false,
        val questionLimit: QuestionLimit = QuestionLimit.FIFTEEN,
        val continents: List<Continent> = emptyList(),
    ) {
        enum class GameMode {
            COUNTRY_CAPITAL, CAPITAL_COUNTRY
        }

        enum class QuestionLimit(val number: String) {
            FIFTEEN("15"),
            THIRTY("30"),
            FIFTY("50"),
            MAX("Max")
        }
    }
}

The converters:

class Converters {

    private val gson = Gson()

    // SCORE
    @TypeConverter
    fun fromScore(value: User.Score): String {
        return gson.toJson(value)
    }

    @TypeConverter
    fun toScore(value: String): User.Score {
        return gson.fromJson(value, User.Score::class.java)
    }

    // SETTINGS
    @TypeConverter
    fun fromSettings(value: List<User.Settings>): String {
        return gson.toJson(value)
    }

    @TypeConverter
    fun toSettings(value: String): List<User.Settings> {
        val type = object : TypeToken<List<User.Settings>>() {}.type
        return gson.fromJson(value, type)
    }

    // QUESTION LIMIT
    @TypeConverter
    fun fromQuestionLimit(value: QuestionLimit): String {
        val type = object : TypeToken<QuestionLimit>() {}.type
        return gson.toJson(value, type)
    }

    @TypeConverter
    fun toQuestionLimit(value: String): QuestionLimit {
        val type = object : TypeToken<QuestionLimit >() {}.type
        return gson.fromJson(value, type)
    }

    // CONTINENT
    @TypeConverter
    fun fromContinentList(value: List<Continent>): String {
        val type = object : TypeToken<List<Continent>>() {}.type
        return gson.toJson(value, type)
    }

    @TypeConverter
    fun toContinentList(value: String): List<Continent> {
        val type = object : TypeToken<List<Continent>>() {}.type
        return gson.fromJson(value, type)
    }

    // GAME MODE SET
    @TypeConverter
    fun fromGameModeSet(value: Set<GameMode>): String {
        val type = object : TypeToken<Set<GameMode>>() {}.type
        return gson.toJson(value, type)
    }

    @TypeConverter
    fun toGameModeSet(value: String): Set<GameMode> {
        val type = object : TypeToken<Set<GameMode>>() {}.type
        return gson.fromJson(value, type)
    }
}

The database entity:

@Database(entities = [User::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

Query giving the error:

    @androidx.room.Query(value = "UPDATE user_table SET settings = :settings WHERE id = :id")
    @org.jetbrains.annotations.Nullable
    public abstract java.lang.Object updateSettings(int id, @org.jetbrains.annotations.NotNull

The DAO:

@Dao
interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun setUser(user: User)

    @Query("SELECT * FROM user_table WHERE id = :id LIMIT 1")
    suspend fun getUserById(id: Int): User?

    @Query("UPDATE user_table SET settings = :settings WHERE id = :id")
    suspend fun updateSettings(id: Int, settings: User.Settings)

    @Query("UPDATE user_table SET score = :score WHERE id = :id")
    suspend fun updateScore(id: Int, score: User.Score)
}

Solution

  • Your DAO function is:

    suspend fun updateSettings(id: Int, settings: User.Settings)
    

    This takes a single User.Settings. Given the rest of your code, this might need to take a List<User.Settings>, to line up with your entity and type converter.

    If you are certain that you want a single User.Settings here, you probably need a converter for a single User.Settings, akin to what you have for User.Score. Right now, your type converter only converts a List<User.Settings>.