Room doesn't detect #Typeconverters - android

This is my only Entity:
#Entity(tableName = "sticker_packs")
class StickerPack(
#PrimaryKey(autoGenerate = true)
val identifier: Int = 0,
var name: String,
var publisher: String,
#TypeConverters(StickerConverter::class)
private var stickers: List<Sticker> = emptyList())
This is my custom Sticker class:
data class Sticker(
val emojis: List<String>,
var imageDir: String,
var size: Long = 0L)
This is my Database class:
#Database(entities = [StickerPack::class], version = 1)
//I get this same error with or without my Typeconverters here
#TypeConverters(StickerConverter::class)
abstract class StickerPacksDatabase: RoomDatabase() {
abstract val stickerPacksDao: StickerPacksDao
companion object{
#Volatile
private var INSTANCE: StickerPacksDatabase? = null
fun getInstance(context: Context): StickerPacksDatabase{
synchronized(this){
var instance = INSTANCE
if(instance == null){
instance =
Room.databaseBuilder(context.applicationContext, StickerPacksDatabase::class.java, "sticker_packs_database")
.build()
INSTANCE = instance
}
return instance
}
}
}
}
And this is my typeconverter
class StickerConverter {
#TypeConverter
fun stickersToJson(stickers: List<Sticker>): String = Gson().toJson(stickers)
#TypeConverter
fun jsonToStickers(json: String): List<Sticker> = Gson().fromJson(json)
private inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, object: TypeToken<T>() {}.type)}
I'm not trying to do anything fancy just converting a list into a string with Gson, yet when trying to build the project I get the following error:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
But that's exaclty what I'm trying to do. I may have made a mistake somewhere but I can't seem to find it. I tried moving the #TypeConverters notation everywhere posible and the problem persists

You need to add the #TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase:
Database(entities = arrayOf(Sticker::class), version = 1)
#TypeConverters(StickerConverter::class)
abstract class AppDatabase : RoomDatabase{
}

Related

Error when using TypeConverter with Room: Cannot figure out how to save this field into database

I get this error:
error: Cannot figure out how to save this field into database. You can
consider adding a type converter for it.
private final java.util.List<com.example.Detail.Stat> stats = null;
I can't figure it out. I have Added the typeconverter to the database, but still get this error. Any ideas what I do wrong?
Entity:
#Entity
data class Detail(
#PrimaryKey val id: Int,
val stats: List<Stat>,
val types: List<String>
){
data class Stat(
val baseStat: Int,
val stat: String
)
}
Typeconverter:
#ProvidedTypeConverter
class StatConverter #Inject constructor(
private val moshi: Moshi
){
#TypeConverter
fun fromJson(value: String): List<Detail.Stat>? {
val listType = Types.newParameterizedType(List::class.java, Detail.Stat::class.java)
val adapter: JsonAdapter<List<Detail.Stat>> = moshi.adapter(listType)
return adapter.fromJson(value)
}
#TypeConverter
fun toJson(type: List<Detail.Stat>?): String {
val listType = Types.newParameterizedType(List::class.java, Detail.Stat::class.java)
val adapter: JsonAdapter<List<Detail.Stat>> = moshi.adapter(listType)
return adapter.toJson(type)
}
}
Database:
#Database(entities = [Detail::class], version = 1, exportSchema = true)
#TypeConverters(StatConverter::class)
abstract class Database : RoomDatabase() {
abstract fun detailDao(): DetailDao
companion object{
const val DATABASE = "database"
}
}
DI module where room is provided:
#Singleton
#Provides
fun provideAppDatabase(
application: Application,
statConverter: StatConverter
): Database {
return Room
.databaseBuilder(application, Database::class.java,
Database.DATABASE
)
.addTypeConverter(statConverter)
.fallbackToDestructiveMigration()
.build()
}
EDIT:
The typeconverter code works fine with the other field (List) in the entity, but not with List.
Apparently, something about your nested data class is causing problems, and so moving Stat out from being nested in Detail helped.
If you have the time, you might try creating a scrap project that illustrates the problem, then file an issue on the issue tracker, attaching that project as a demonstration of the problem. I don't see anything that quite matches, but there are a lot of issues, so perhaps I missed it.
I didn't run your code or test this out, but from eyeballing it here, is it possible it's the difference between nullable List<Detail.Stat>? in the type converter and non-nullable List<Stat> in the entity? Either make entity nullable or type-converter non-nullable and see if it works.

Why do I have error while building project?

I watched the youtube video and implemented database in my project
Database.kt:
#Database(entities = [Result::class], version = DATABASE_VERSION, exportSchema = false)
abstract class ResultDatabase : RoomDatabase() {
abstract fun resultDao(): ResultDao
companion object {
#Volatile
private var INSTANCE: ResultDatabase? = null
fun getDatabase(context: Context): ResultDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ResultDatabase::class.java,
TABLE_NAME
).build()
INSTANCE = instance
return instance
}
}
}
}
ResultClass.kt:
#Entity(tableName = TABLE_NAME)
data class Result(private val id: Int,
private val cheatingEnabled: Boolean,
private val cheatingSuccessful: Boolean,
private val min: Long,
private val max: Long,
private val result: Long,
private val time: LocalDateTime
)
ResultDao.kt:
#Dao
interface ResultDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun addResult(result: Result)
#Query(value = "SELECT * FROM random_data ORDER BY id ASC")
fun getAllResults(): LiveData<List<Result>>
}
Repo.kt
class ResultsRepository(private val resultDao: ResultDao) {
val resultHistory: LiveData<List<Result>> = resultDao.getAllResults()
fun addResult(result: Result) {
resultDao.addResult(result)
}
}
Function that inserts data to database:
private fun insertDataToDatabase(min: Long,
max: Long,
result: Long,
cheatingEnabled: Boolean,
cheatingSuccessful: Boolean
) {
val time = LocalDateTime.now()
val resultEntity = Result(0, cheatingEnabled, cheatingSuccessful, min, max, result, time)
viewModelScope.launch {
repository.addResult(resultEntity)
}
}
Error:
C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:14: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.time.LocalDateTime time = null;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:8: error: Cannot find getter for field.
private final int id = 0;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:9: error: Cannot find getter for field.
private final boolean cheatingEnabled = false;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:10: error: Cannot find getter for field.
private final boolean cheatingSuccessful = false;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:11: error: Cannot find getter for field.
private final long min = 0L;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:12: error: Cannot find getter for field.
private final long max = 0L;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:13: error: Cannot find getter for field.
private final long result = 0L;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:14: error: Cannot find getter for field.
private final java.time.LocalDateTime time = null;
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:7: error: An entity must have at least 1 field annotated with #PrimaryKey
public final class Result {
^C:\Users\Maxim\AndroidStudioProjects\AdvancedNumberGenerator\app\build\tmp\kapt3\stubs\debug\com\example\advancednumbergenerator\database\Result.java:14: error: Cannot figure out how to read this field from a cursor.
private final java.time.LocalDateTime time = null;
^
How can I fix this error? Can it be connected with using LocalDateTime datatype and Long instead of int? But I don't even start inserting, I get errors while building. I saw similar questions and there people adviced to use json objects, but man from youtube has project without json and withoutany errors.
Can it be connected with using LocalDateTime datatype and Long instead of int?
Yes.
But I don't even start inserting, I get errors while building
Room uses an annotation processor — that is the kapt statement you added to your dependencies. That annotation processor does not know what to do with a LocalDateTime. And, it cannot work with private Kotlin properties.
How can I fix this error?
Start by removing the private keyword from those properties on Result. That should reduce the errors to 2, both for LocalDateTime.
Then, you will need to teach Room how to work with LocalDateTime.
For that, you will need a class with a pair of #TypeConverter functions that can convert between LocalDateTime and... something. In this sample, I have type converters between Instant and Long:
import androidx.room.TypeConverter
import java.time.Instant
class TypeTransmogrifier {
#TypeConverter
fun fromInstant(date: Instant?): Long? = date?.toEpochMilli()
#TypeConverter
fun toInstant(millisSinceEpoch: Long?): Instant? = millisSinceEpoch?.let {
Instant.ofEpochMilli(it)
}
}
Then, you will need to add a #TypeConverters annotation (note the trailing s) to something to register your class with the #TypeConverter annotations. One simple place to do this is on your #RoomDatabase class, as I do in this sample:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
private const val DB_NAME = "stuff.db"
#Database(entities = [ToDoEntity::class], version = 1)
#TypeConverters(TypeTransmogrifier::class)
abstract class ToDoDatabase : RoomDatabase() {
abstract fun todoStore(): ToDoEntity.Store
companion object {
fun newInstance(context: Context) =
Room.databaseBuilder(context, ToDoDatabase::class.java, DB_NAME).build()
}
}
Given those two things, in my sample project, any of my entities can use Instant. You would do the same thing, just for LocalDateTime (or, perhaps, switch your entities from using LocalDateTime to using Instant).
You can read more about this in the documentation.

Type converter for Room Database not working on class which has object from another class

I have the following class:
#Entity(tableName = "dutyday_table")
data class DutyDay(
#PrimaryKey
val date: Date,
val showTime: Time,
val closingTime:Time
)
and another class which uses objects from class "DutyDay":
#Entity(tableName = "tour_table")
data class Tour(
#PrimaryKey
val day1: DutyDay?,
val day2: DutyDay?,
val day3: DutyDay?,
val day4: DutyDay?,
val day5: DutyDay?,
val day6: DutyDay?,
val day7: DutyDay?,
val totalHours: Time
)
and the following converter:
class Converters {
#TypeConverter
fun longToDate(value: Long?): Date? {
return value?.let { Date(it) }
}
#TypeConverter
fun dateToLong(date: Date?): Long? {
return date?.time
}
#TypeConverter
fun longToTime(value: Long?): Time? {
return value?.let { Time(it) }
}
#TypeConverter
fun timeToLong(date: Time?): Long? {
return date?.time
}
}
The converter is properly annotated in the database, I have used Room Database and converters before they normaly work fine. But with this example Android Studio is complaining that it cannot figure out how to store the fields from class "Tour", I guess because it uses the DutyDays created in class "DutyDay". Do I somehow have to create another converter for the properties in "Tour"?
Thanks for your help .
There are 3 objects in your entities that are unknown for Room - Date, Time, DutyDay. You've described TypeConverter just for Date and Time, so the problem is that Room doesn't know how to persist DutyDay.
I think you choices are:
Add one more converter for DutyDay (to use Gson-converting to string, for example. Here is link with similar decision).
To use foreign keys inside Tour table instead of DutyDay's references.

Get Room object via companion using reflection and class name

As the title stands, I want to get a singleton instance via reflection using the class name. Let's say I have this class:
abstract class MyRoomDatabase : RoomDatabase() {
abstract fun exampleEntityDao() : ExampleEntityDao
companion object {
#Volatile
private var INSTANCE: MyClass? = null
fun getDatabase(context: Context): MyClass {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(context.applicationContext, MyRoomDatabase::class.java, "db").build()
INSTANCE = instance
instance
}
}
}
}
Now, knowing the class name val strClass = MyRoomDatabase::class.qualifiedName.toString()
I would like to achieve:
MyRoomDatabase.getDatabase(this).getExampleEntityDao().doSomeStuff() but via reflection.
I tried this:
val strClass = MyRoomDatabase::class.qualifiedName.toString()
val classFromStr = Class.forName(strClass)
val companion = classFromStr::class.companionObject
in that case, companion is null.
In the other case this: val companion = MyRoomDatabase::class.companionObject gives me the actual companion but I cannot do that because in the place where it should be done the compiler does know only the class name.
classFromStr::class will return the class of classFromStr which is Class and doesn't have a companion object.
val companion = classFromStr.kotlin.companionObject
should work.

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...
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 {
...
}
}
}

Categories

Resources