Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Room cannot recognize TypeConverer for List

Here's my Entity:

@Entity(tableName = "commits")
data class Commit(
        @PrimaryKey
        @ColumnInfo(name = "hash")
        val hash: String,
        @ColumnInfo(name = "changes", typeAffinity = ColumnInfo.BLOB)
        var changes: List<DbChange>
)

And here's the converters:

class Converters {
    companion object {
        @JvmStatic
        @TypeConverter
        fun changesToByteArray(changes: List<DbChange>): ByteArray {
            ...
        }

        @JvmStatic
        @TypeConverter
        fun byteArrayToChanges(bytes: ByteArray): List<DbChange> {
            ...
        }
    }
}

And I've already added Converters class to the annotations:

@Database(entities = [Commit::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppRoomDatabase : RoomDatabase() {
    ...
}

But the compiler still complains:

e: /home/perqin/workspaces/cent-budget/cent-budget/app/build/tmp/kapt3/stubs/eaDebug/com/perqin/centbudget/data/sync/commit/Commit.java:20: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
    private java.util.List<? extends com.perqin.centbudget.data.sync.changes.DbChange> changes;

How to solve this issue? I have no ideas at all...

like image 271
Perqin Avatar asked Oct 16 '22 21:10

Perqin


1 Answers

This is actually Kotlin's fault rather than Room's.
As you can see in your error message you get List<? extends DbChange> instead of List<DbChange> for changes field and thus Room tried to find TypeConverters for byte[] -> List<? extends DbChange> and List<? extends DbChange> -> byte[], but fails to do so.

That's because changesToByteArray method argument type is generated as List<? extends DbChange>, but type of byteArrayToChanges method is still List<DbChange> and thus you get a compilation error.

I would recommend this article if you want read more.

Solution 1:
Use @JvmSuppressWildcards
Annotate Commit class so that type without wildcards will be generated for changes field and it's setter:

@Entity(tableName = "commits")
@JvmSuppressWildcards
data class Commit(
    @PrimaryKey
    @ColumnInfo(name = "hash")
    val hash: String,

    @ColumnInfo(name = "changes", typeAffinity = ColumnInfo.BLOB)
    var changes: List<DbChange>
)

Annotate List's type for same reason:

class Converters {
    companion object {
        @JvmStatic
        @TypeConverter
        fun changesToByteArray(changes: List<@JvmSuppressWildcards DbChange>): ByteArray {
            ...
        }

        @JvmStatic
        @TypeConverter
        fun byteArrayToChanges(bytes: ByteArray): List<DbChange> {
            ...
        }
    }
}

Solution 2:
Create wrapper class for your list like:

data class DbChanges(val list: List<DbChange>)

and then update Commit class:

@Entity(tableName = "commits")
data class Commit(
    @PrimaryKey
    @ColumnInfo(name = "hash")
    val hash: String,

    @ColumnInfo(name = "changes", typeAffinity = ColumnInfo.BLOB)
    var changes: DbChanges
)

and write new TypeConverters:

class Converters {
    companion object {
        @JvmStatic
        @TypeConverter
        fun changesToByteArray(changes: DbChanges): ByteArray {
            ...
        }

        @JvmStatic
        @TypeConverter
        fun byteArrayToChanges(bytes: ByteArray): DbChanges {
            ...
        }
    }
}
like image 136
Michał Baran Avatar answered Oct 21 '22 00:10

Michał Baran