Android Room Cannot figure out how to save this field into database - android

I'm try to using save values to database via room persistance library. My SettingsValueModelConverter is wrong somethings are missing. How I can save SettingsKeyContract objects best way?
Logcat:
SettingsModel
Error:(14, 1) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Codes:
object SETTING_CONS{
const val TABLE = "Content_Setting"
const val ID = "uid"
const val KEY = "key"
const val VALUE = "value"
}
class SettingsValueModel(var value: SettingsKeyContract)
class SettingsValueModelConverter {
#TypeConverter
fun fromString(value: String): SettingsKeyContract = Gson().fromJson(value, object : TypeToken<SettingsKeyContract>() {}.type)
#TypeConverter
fun fromModel(value: SettingsKeyContract): String = Gson().toJson(value)
}
#Entity(tableName = SETTING_CONS.TABLE)
class SettingsModel(#ColumnInfo(name = SETTING_CONS.KEY) #SETTINGS var key: String,
#ColumnInfo(name = SETTING_CONS.VALUE) var value: SettingsValueModel) {
#ColumnInfo(name = SETTING_CONS.ID)
#PrimaryKey(autoGenerate = true)
var uid: Int = 0
}
#Dao
interface SettingsDao {
#Query("SELECT * FROM ${SETTING_CONS.TABLE} WHERE ${SETTING_CONS.ID} = :key")
fun get(#SETTINGS key: String): LiveData<SettingsModel>
#get:Query("SELECT * FROM ${SETTING_CONS.TABLE}")
val all: LiveData<MutableList<SettingsModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(model: SettingsModel): Long
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg models: SettingsModel): LongArray
#Query("DELETE FROM ${SETTING_CONS.TABLE} WHERE ${SETTING_CONS.KEY} = :key")
fun delete(#SETTINGS key: String)
#Query("DELETE FROM ${SETTING_CONS.TABLE}")
fun clear()
}
interface SettingsKeyContract { val key: String }
interface TypeSettingsKeyContract<out T : Any> : SettingsKeyContract { val default: T }
sealed class SETTING(override val key: String) : SettingsKeyContract {
object FIRST_LAUNCH_DATE : SETTING("first_launch_date"), TypeSettingsKeyContract<Long> { override val default = 0L }
}

Where do you define your database?
You should have the option there to specify which converter to use, like such :
#Database(entities = arrayOf(SettingsModel::class) , version = 1, exportSchema = false)
#TypeConverters(SettingsValueModelConverter::class)
abstract class AppDatabase: RoomDatabase() {
abstract fun SettingsDao(): SettingsDao
}

Related

Type converter error on Android Room Database

java.lang.IllegalArgumentException: A required type converter (class SourceConverter) for ArticlesDao is missing in the database configuration.
I have a crash when compiling (runtime) my application because according to the error displayed, the converter is missing in the configuration of my database. how could I solve this? I need help please.
The source code below:
SourceConverter.kt
#ProvidedTypeConverter
internal class SourceConverter {
#TypeConverter
fun stringToSource(string: String?): SourceEntity = Gson().fromJson(string, SourceEntity::class.java)
#TypeConverter
fun sourceToString(list: SourceEntity?): String = Gson().toJson(list)
}
ArticleEntity.kt
#Entity(tableName = "article")
data class ArticleEntity(
#TypeConverters(SourceConverter::class)
#SerializedName("source")
var source: SourceEntity? = null,
#SerializedName("author")
var author: String? = null,
#SerializedName("title")
var title: String? = null,
#SerializedName("description")
var description: String? = null,
#SerializedName("url")
#NonNull #PrimaryKey var url: String,
#SerializedName("urlToImage")
var urlToImage: String? = null,
#SerializedName("publishedAt")
var publishedAt: String? = null,
#SerializedName("content")
var content: String? = null
)
ArticlesDao.kt
#Dao
interface ArticlesDao {
#Query("select * from article where url = :primaryId")
fun findByPrimaryId(primaryId: String?): ArticleEntity?
#Query("DELETE FROM article WHERE url = :primaryId")
fun deleteByPrimaryId(primaryId: String?): Int
#Query("SELECT * FROM article")
fun getAllArticles(): Flow<List<ArticleEntity>>
#Query("DELETE FROM article")
fun clear()
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(articleEntity: ArticleEntity?): Long
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg articleEntities: ArticleEntity?): LongArray?
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(articleEntityList: List<ArticleEntity?>?): LongArray?
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(articleEntity: ArticleEntity?): Int
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(vararg articleEntities: ArticleEntity?): Int
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(articleEntityList: List<ArticleEntity?>?): Int
#Delete
fun delete(articleEntity: ArticleEntity?): Int
}
AppDatabase.kt
#Database(entities = [ArticleEntity::class, SourceEntity::class], version = 1, exportSchema = false)
#TypeConverters(SourceConverter::class, ArticleConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getArticlesDao(): ArticlesDao
abstract fun getSourcesDao(): SourcesDao
companion object {
#Synchronized
fun getInstance(context: Context): AppDatabase {
if (sInstance == null) {
sInstance = Room
.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "infotify.db"
)
.addTypeConverter(SourceConverter::class.java)
.build()
}
return sInstance as AppDatabase
}
}
}
There are three ways to add type converter to room database. But you should make sure you using one method only or it will get error:
add annotation at the type converter class header #ProvidedTypeConverter
Room.databaseBuilder.addTypeConverter(ConverterHelper::class)
add annotation at the RoomDatabase class header #TypeConverters(ConverterHelper::class)
Instead of
Room.databaseBuilder.addTypeConverter(ConverterHelper::class)
Use this:
Room.databaseBuilder.addTypeConverter(ConverterHelper())

How to set data in Model?

I have one entity "drinks" which have [id;name;thumb] and I`m using these entities for 2 response calls. One response returns me a NonAlcohol list of drinks, another AlcoholList, I'm using Room for caching the data. But when I run the app, I saw that my lists merged, after some thought, I found a solution to this problem, I added one Boolean field to my entity "alcoholStatus".But I can't understand how to set the data into this variable correctly using this AccessDataStrategy. I'm new to Android, and this is my learning project. Please give me the right way how to solve this problem.
https://github.com/YaroslavSulyma/LetsDrink/tree/master/app/src/main/java/com/example/letsdrink
Thanks a lot!
Entity
#Entity(tableName = "drinks")
data class DrinksModel(
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("idDrink")
#PrimaryKey
val idDrink: Int,
var alcohol: Boolean
)
DataAccessStrategyCode
fun <T, A> performGetOperation(
databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit
): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery.invoke().map { Resource.success(it) }
emitSource(source)
val responseStatus = networkCall.invoke()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
emitSource(source)
}
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
Repository
class CocktailsRepository #Inject constructor(
private val remoteDataSource: CocktailsRemoteDataSource,
private val localDataSource: CocktailsDao
) {
fun getAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllAlcoholicCocktails()},
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
fun getNonAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getNonAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllNonAlcoholicCocktails() },
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
}
DAO
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 'true'")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 'false'")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
RemoteDataSource
class CocktailsRemoteDataSource #Inject constructor(private val iCocktailApisService: ICocktailApisService) :
BaseDataSource() {
suspend fun getAllAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Alcoholic") }
suspend fun getAllNonAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Non_Alcoholic") }
}
First: I strongly recommend that you define separate data classes for your remote and local model classes and do the mapping between them when needed, for example:
Remote data model:
data class DrinkRemoteModel(
#SerializedName("idDrink")
val idDrink: Int,
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("alcohol")
var alcohol: Boolean
)
Local data model:
#Entity(tableName = "drinks")
data class DrinkLocalModel(
#PrimaryKey
#ColumnInfo(name = "idDrink")
val idDrink: Int,
#ColumnInfo(name = "strDrink")
val strDrink: String,
#ColumnInfo(name = "strDrinkThumb")
val strDrinkThumb: String?,
#ColumnInfo(name = "alcohol")
var alcohol: Boolean
)
Back to your implementation: I think what causing the problem is that Room maps Boolean fields in your entity to an integer column, 1 for true, and 0 for false, so try changing your querys in your DAO like following:
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 1")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 0")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
Alternatively: you can substitute your getAlcoholicCocktails and getNonAlcoholicCocktails with one DAO function, like this:
#Query("SELECT * FROM drinks WHERE alcohol = :isAlcoholic")
fun getCocktails(isAlcoholic : Boolean = true): LiveData<List<DrinksModel>>

Cannot figure out how to save this field into database. I have written data class,dao and typeconverters, but still unable to detect error

MyData.kt
#Entity(tableName = "my_table")
data class MyData(
#PrimaryKey(autoGenerate = true)
var id: Long = 0L,
#ColumnInfo(name = "ListData")
#TypeConverters(DataTypeConverter::class)
var mList: List<User> = emptyList(),
#Embedded
var user: User
)
MyDataDao.kt
#Dao
interface MyDataDao {
#Insert
suspend fun insert(data: MyData)
#Update
suspend fun update(data: MyData)
#Query("SELECT * FROM my_table")
fun getAll(): LiveData<List<MyData>>
}
DataTypeConverter.kt
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class DataTypeConverter {
companion object {
inline fun <reified T> Gson.fromJson(json: String) =
fromJson<T>(json, object : TypeToken<T>() {}.type)
#TypeConverter
fun stringToList(data: String?): List<User> {
data?.let {
return Gson().fromJson(data)
}
return emptyList()
}
#TypeConverter
fun listToString(users: List<User>): String {
return Gson().toJson(users)
}
}
}
User.kt
data class User(
#ColumnInfo(name = "first_name")
val firstName: String,
#ColumnInfo(name = "last_name")
val lastName: String
)
on build getting this error
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Even after using typeconverters the problem is persistent
I think your DataTypeConverter is wrong you can try change like this :
class DataTypeConverter{
var gson = Gson()
#TypeConverter
fun stringToUserList(data: String?): List<User?>? {
if (data == null) {
return Collections.emptyList()
}
val listType: Type =
object : TypeToken<List<User?>?>() {}.type
return gson.fromJson<List<User?>>(data, listType)
}
#TypeConverter
fun userDetailListToString(someObjects: List<User?>?): String? {
return gson.toJson(someObjects)
}
}
Don't forget to add #TypeConverters(DataTypeConverter::class) to #Database

Room - save custom object list

I'm trying to save List<Object> into Room database.
I get following error:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private java.util.List<xxx.models.OBJECTX> carList;
Room implementation:
#Entity(tableName = "nameOfTable")
class CachedObjectX(
#PrimaryKey
#ColumnInfo(name = "id") val id: Long,
#ColumnInfo(name = "list")
var carList: List<ObjectX>
)
#Dao
interface CachedObjectXDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(list: ArrayList<CachedObjectX>)
#Delete
fun delete(list: ArrayList<CachedObjectX>)
#Query("SELECT * FROM ...")
fun getAll(): ArrayList<CachedObjectX>
}
class CachedObjectXConverter {
companion object {
var gson = Gson()
#TypeConverter
#JvmStatic
fun toInsuredVehicle(json: String): List<ObjectX> {
val type = object : TypeToken<List<ObjectX>>() {}.type
return gson.fromJson(json, type)
}
#TypeConverter
#JvmStatic
fun toJson(torrent: List<ObjectX>): String {
val type = object: TypeToken<List<ObjectX>>() {}.type
return gson.toJson(torrent, type)
}
}
}
#Database(entities = [CachedObjectX::class], version = 1, exportSchema = false)
#TypeConverters(CachedObjectXConverter::class)
abstract class CachedObjectXDb : RoomDatabase() {
companion object {
private const val DB_NAME = "CachedObjectX.db"
val instance: CachedObjectXDb by lazy {
Room.databaseBuilder(
getContext(),
CCchedObjectXDb::class.java,
DB_NAME
).build()
}
}
abstract fun getDao(): CachedObjectXDao
}
Interesting, that I've added TypeConverter, but it still throws error. What is wrong with my implementation? Just started with Room, so there is high chance that something is wrong. Thanks in advance.
You have to make the pojo of ObjectX be an entity too

Android Room TypeConverter not working

I'm trying to save some data in my Room database but it keeps showhing me an error, here's the code:
MovieDao.kt
#Dao
interface MoviesDao {
#Query("SELECT * from movie")
fun getAll() : LiveData<List<Movie>>
#Update
fun update(movie: Movie)
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(movie: Movie)
}
MoviesDatabase.kt
#Database(entities = [Movie::class], version = 1, exportSchema = false)
#TypeConverters(TorrentConverter::class, GenreConverter::class)
abstract class MoviesDatabase : RoomDatabase() {
companion object {
private var instance: MoviesDatabase? = null
fun getDatabase(context: Context) : MoviesDatabase{
if(instance == null) {
instance = Room.databaseBuilder(context.applicationContext, MoviesDatabase::class.java,
"movies_database").build()
}
return instance as MoviesDatabase
}
}
abstract fun getMoviesDao() : MoviesDao
}
MovieModels.kt
#Entity(tableName = "movie")
data class Movie(
val url: String,
#PrimaryKey
val imdb_code: String,
val title: String,
val year: Int,
val rating: Float,
val runtime: Int,
#TypeConverters(GenreConverter::class)
val genres: List<String>?,
val synopsis: String,
val yt_trailer_code: String,
val language: String,
val mpa_rating: String,
val medium_cover_image: String,
val large_cover_image: String,
val state: String,
#TypeConverters(TorrentConverter::class)
var torrents: List<Torrent>,
var saved: Boolean = false,
var marked: Boolean = false
) : Serializable
data class Torrent(
val url: String,
val hash: String,
val quality: String,
val seeds: Int,
val peers: Int,
val size: String,
val size_bytes: Long
) : Serializable
TypeConverters.kt
class TorrentConverter {
#TypeConverter
fun toTorrent(json: String): Torrent {
val type = object : TypeToken<Torrent>() {}.type
return Gson().fromJson(json, type)
}
#TypeConverter
fun toJson(torrent: Torrent) = Gson().toJson(torrent)
}
class GenreConverter {
#TypeConverter
fun toGenre(json: String): List<String> {
val type = object : TypeToken<List<String>>() {}.type
return Gson().fromJson(json, type)
}
#TypeConverter
fun toJson(genres: List<String>) = Gson().toJson(genres)
}
the error shows me:
error: Cannot figure out how to save this field into database. You can
consider adding a type converter for it.
private java.util.List torrents;
Can someone please help me to figure out why is this error happening? Thanks alot.
It's happening because your TorrentConverter is returning and getting the wrong types.
The method toTorrent should return a List<Torrent> and the method toJson should receive a List<Torrent>
Try this TypeConverter:
class TorrentConverter {
#TypeConverter
fun toTorrent(json: String): List<Torrent> {
val type = object : TypeToken<List<Torrent>>() {}.type
return Gson().fromJson(json, type)
}
#TypeConverter
fun toJson(torrent: List<Torrent>): String {
val type = object: TypeToken<List<Torrent>>() {}.type
return Gson().toJson(torrent, type)
}
}

Categories

Resources