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.
Related
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.
I have searched for quite a while and am at a loss. My app makes some fairly routine API calls and stores the results in a Room DB, this all works perfectly until I try and utilize a TypeConverter for a date field by changing the data type of a parameter in one of my classes/tables. The problem is I'm not getting any error, it's simply not storing any data in the Room database the moment I switch the type. I swear I've implemented this correctly based on everything I've read and seen, I tested the converter function with mock data as my API returns and that is working. I can't seem to figure out what Room is doing and where this is breaking down.
Here is the relevant portion of my data class, if dateAdded were declared as String? everything works fine:
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import java.util.*
#Entity
data class Cocktail (
...
#ColumnInfo(name = "date_added")
#SerializedName("dateadded")
val dateAdded: Date?
)
API returns:
[
{
...
"dateadded": "2020-04-03 13:15:56"
}
]
Database:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.goodcall.goodcall.data.models.*
#Database(
entities = arrayOf(
Cocktail::class,
CocktailRecipe::class,
CocktailIngredient::class,
IngredientType::class,
Lists::class,
ListsDrinksItems::class
),
version = 1,
exportSchema = false
)
#TypeConverters(Converters::class)
abstract class GoodCallDatabase : RoomDatabase() {
abstract fun goodCallDao(): GoodCallDao
companion object {
#Volatile private var instance: GoodCallDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also {
instance = it
}
}
private fun buildDatabase(context: Context) = Room.databaseBuilder(
context.applicationContext,
GoodCallDatabase::class.java,
"goodcalldatabase.db"
)
.build()
}
}
Converter class:
import androidx.room.TypeConverter
import java.text.SimpleDateFormat
import java.util.*
class Converters {
#TypeConverter
fun fromTimestampStr(timeStamp: String?): Date? {
if(timeStamp == null){
return null
} else {
val date = SimpleDateFormat("YYYY-MM-dd HH:mm:ss").parse(timeStamp)
return date
}
}
#TypeConverter
fun toTimestampStr(date: Date?): String? {
if(date == null){
return null
} else {
val format = SimpleDateFormat("YYYY-MM-dd HH:mm:ss")
return format.format(date)
}
}
}
Appreciate any and all help here, I'm pretty much a novice with Android so hoping this is a simple fix.
I think your problem isn't with the Room, Did you add DateDeserializer to your GsonBuilder?
Kotlin
class DateDeserializer : JsonDeserializer<Date> {
#Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Date {
}}
Java
public class DateDeserializer implements JsonDeserializer<Date> {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
}}
if had this is implemented, I suggest changing your file signature from class to object.
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{
}
In order to support synchronization of time using NTP for time sensitive data, I've added a Volatile property to our RoomDatabase from where all update operations can request a "timestamp". But I was wondering if this is threadsafe in regards to using Room with suspending functions and transactions? Also not quite sure it actually has to be marked as Volatile as every access should produce a new java.time.Clock.
Example:
abstract class AppDatabase : RoomDatabase() {
abstract val chatRepository: ChatRepository
abstract val taskRepository: TaskRepository
abstract val userSampleRepository: UserRepository
companion object {
private const val DB_NAME = "example_database"
#Volatile
internal var clock: Clock = Clock.systemDefaultZone()
private set
...
#JvmStatic
fun withClock(clock: Clock) {
synchronized(this) {
this.clock = clock
}
}
}
}
When used:
abstract class TaskRepository
...
private suspend fun updateAll(tasks: List<TaskEntity>) = updateAllInternal(
tasks.map {
TaskEntity.Builder()
.copyOf(it)
.withUpdatedAt(OffsetDateTime.now(AppDatabase.clock))
.create()
}
)
...
}
Related passing tests:
#Test
fun whenRequestingDefaultClock_shouldCreateUpdatedTimestamp() {
val firstTimestamp = Instant.now(AppDatabase.clock)
sleep(1) // Adding 1ms delay to ensure Clock is not fixed and returns a different timestamp
val secondTimestamp = Instant.now(AppDatabase.clock)
assertThat(firstTimestamp).isLessThan(secondTimestamp)
}
#Test
fun whenSwitchingToFixedClock_shouldUseSameTimestamp() {
val instant = Instant.now()
val clock = Clock.fixed(instant, Clock.systemDefaultZone().zone)
AppDatabase.withClock(clock)
val firstTimestamp = Instant.now(AppDatabase.clock)
sleep(1) // Adding 1ms delay to ensure Clock is fixed and returns a fixed timestamp
val secondTimestamp = Instant.now(AppDatabase.clock)
assertThat(firstTimestamp).isEqualTo(secondTimestamp)
}
I am using Room. I need to use SELETC, INSERT, DELETE all.
This is what I implemented:
#Dao
interface UserDao {
#Query("SELECT * FROM user WHERE m_id IN (:m_id)")
fun loadAllByIds(userSeqs: IntArray?): List<User?>?
#Query("SELECT * FROM user")
val all: List<User?>?
}
#Entity(tableName = "user")
data class User (
#PrimaryKey(autoGenerate = true) val seq: Long,
// this name is used in dao as column name
#ColumnInfo(name = "m_id") val mId: String?,
#ColumnInfo(name = "k_id") var kId: String?,
#ColumnInfo(name = "created_at") var createdAt: String?
)
#Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
public static final String DB_NAME = "MyDatabase.db";
private static MyDatabase INSTANCE;
public abstract UserDao userDao();
public static MyDatabase getMyDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context, MyDatabase.class, DB_NAME).build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
#Query("SELECT * FROM deliver") says
This not applicable to target 'member property without backing field or delegate'
#get:Query("SELECT * FROM deliver") make it disappear. But, I don't know why it does. Does it solve the problem? What does it do?
The #Query annotation is to be placed on a method. A kotlin property is kind of "a field + getter method + setter method", but it's not a field or a method per se. When you specify the annotation target via #get:Query, you are basically telling the compiler to put this annotation on the property's getter method, which makes the whole thing work.
I had the same issue, I figure out a solution by checking my import.
I am using retrofit and Room, by mistake I imported retrofit #Query version instead of a room. Please check your import as well.