Room TypeConverter with constructor - android

I have Room TypeConverter and I need to inject parameter to it's constructor
class RoomConverters(moshi Moshi) {
#TypeConverter
fun fromUserActionLog(data: UserActionLogModel): String {
return moshi.adapter(UserActionLogModel::class.java).toJson(data)
}
#TypeConverter
fun toUserActionLog(json: String): UserActionLogModel {
return moshi.adapter(UserActionLogModel::class.java).fromJson(json)}
}
}
But when I can not annotate TypeConverter to database object with contructor;
#Database(entities = [SsidModel::class], version = 1, exportSchema = false)
#TypeConverters(RoomConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun ssidDao(): SsidDao
}
Is there any way to achieve this?

You can create Room TypeConverter with constructor parameters from version 2.3.0-alpha03
Release notes:
Room now has APIs for providing instances of type converters such that
the app can control their initialization. To mark a type converter
that will be provided to Room use the new annotation
#ProvidedTypeConverter
https://developer.android.com/jetpack/androidx/releases/room#2.3.0-alpha03
In your case you should add #ProvidedTypeConverter to RoomConverter
#ProvidedTypeConverter
class RoomConverters(moshi: Moshi)
Create converter at db creation time and pass it to database builder:
val roomConverter = RoomConverters(Moshi())
val db = Room.databaseBuilder()
.addTypeConverter(roomConverter)
.build()
Also you can use DI framework e.g. Dagger2

Now that 2.3.0 of Room library is released. It is possible to instantiate Room type converters and provide them to database builder.
Add #ProvidedTypeConverter annotation to the TypeConverter class.
#ProvidedTypeConverter
class RoomConverter(moshi Moshi) {
#TypeConverter
fun fromUserActionLog(data: UserActionLogModel): String {
return moshi.adapter(UserActionLogModel::class.java).toJson(data)
}
#TypeConverter
fun toUserActionLog(json: String): UserActionLogModel {
return moshi.adapter(UserActionLogModel::class.java).fromJson(json)}
}
}
Mention the TypeConverter in the #TypeConverter annotation of the database abstract class.
#Database(entities = [/* entities here */], version = 1, exportSchema = false)
#TypeConverters(RoomConverters::class)
abstract class AppDatabase : RoomDatabase() {
......
// your code here
......
}
Now build the database using the static method databaseBuilder of Room class and provide TypeConverter using the method addTypeConverter()
val roomConverter = RoomConverter(moshi)
val appDb = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, DB_NAME
)
.addTypeConverter(roomConverter)
.build()

I use dagger-android, and faced same problem.
Solution is when creating AppDatabase
#Provides #Reusable
fun provideDatabase(context: Context, moshi: Moshi): AppDatabase =
Room.databaseBuilder(...).build().apply { AppDatabase.moshi = moshi }
AppDatabase is simple RoomDatabase:
#Database(
entities = [OrderEntity::class],
version = 1,
exportSchema = false
)
#TypeConverters(DbConverters::class)
abstract class AppDatabase : RoomDatabase() {
companion object {
lateinit var moshi: Moshi
}
abstract fun orderDao(): OrderDao
}
Then use this companion object in converter:
class DbConverters {
#TypeConverter
fun orderInfoToString(orderInfo: OrderInfo?): String? =
AppDatabase.moshi.adapter(OrderInfo::class.java).toJson(orderInfo)
#TypeConverter
fun stringToOrderInfo(value: String): OrderInfo? =
AppDatabase.moshi.adapter(OrderInfo::class.java).fromJson(value)
}
This is looking ugly, I guess, but works. Maybe using static/companion object with #Reuseable scope is a bad idea.
Moshi, though, is provided using #Singleton scope in AppModule, so basically live through entire application life

I checked documentation even for the newest rc version (2.2.0-rc01) and there is no way to do that. I think it's a joke that we can't register custom TypeConverters in DatabaseBuilder, so I created bug for that on Google issue tracker.

As of August 11th 2020 there's still no fix for this from Google. Check either of these workarounds in the meantime.
https://issuetracker.google.com/issues/142086380
https://issuetracker.google.com/issues/121067210

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.

Is it possible to have multiple files dao to the same database on android?

I would like to create multiple DAOS files to better organize the project, even though I have only one database. When I try to do something along these lines, it works:
#Database(version = 1, entities = [ContaBancaria::class])
abstract class CArchDatabase : RoomDatabase() {
abstract fun contaBancariaDao(): ContaBancariaDao
companion object {
fun createDatabase(context: Context): RoomDatabase {
return Room.databaseBuilder(context, CArchDatabase::class.java, "CArchDatabase.db")
.fallbackToDestructiveMigration()
.build()
}
}
}
However, when I try something like this, it doesn't work:
#Database(version = 1, entities = [ContaBancaria::class, entity1, entity2,entity3 ....])
abstract class CArchDatabase : RoomDatabase() {
abstract fun contaBancariaDao(): ContaBancariaDao
abstract fun dao1(): dao1
abstract fun dao2(): dao2
abstract fun dao3(): dao3
abstract fun dao4(): dao4
.
.
.
companion object {
fun createDatabase(context: Context): RoomDatabase {
return Room.databaseBuilder(context, CArchDatabase::class.java, "CArchDatabase.db")
.fallbackToDestructiveMigration()
.build().dao1().dao2().dao3()....
}
}
}
I'm using a koin singleton to create my database
val cacheDataModule = module {
single { CArchDatabase.createDatabase(androidContext()) }
factory<ContaBancariaCacheDataSource> {
ContaBancariaCacheDataSourceImpl(
contaBancariaDao = get()
)
}
}
Each one of your database DAO must have a #Dao class where operations are defined (check documentation: https://developer.android.com/training/data-storage/room#dao)
From your code I assume you already have a ContaBancariaDao class. If you want to create more DAO, you need to create new classes similar to that one.
Then you only need to add a new fun in your CArchDatabase.
If you also want to add new entities, remember to add them in entities array in #Database annotation.
#Database(entities = arrayOf(ContaBancaria::class, Other::class), version = 1)
abstract class CArchDatabase : RoomDatabase() {
abstract fun contaBancariaDao(): ContaBancariaDao
abstract fun otherDao(): OtherDao
companion object {
fun createDatabase(context: Context): RoomDatabase {
return Room.databaseBuilder(context, CArchDatabase::class.java, "CArchDatabase.db")
.fallbackToDestructiveMigration()
.build()
}
}
}

error: Type of the parameter must be a class annotated with #Entity or a collection/array of it

I have looked at other posts regarding the same, but i don't follow what mistake i must be doing.
I have the same name declared in Entity for the class as well as in Database declaration file too.
I'm also passing the same type of parameter as the entity class name, and still i'm getting this error thrown and it is not compiling.
Here's my code. TIA
#Entity(tableName = "current_task_table")
data class CurrentTask (
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "task_name") val taskName: String
)
#Dao
interface CurrentTaskDao {
#Query("SELECT * FROM current_task_table")
fun getAllCurrentTask(): LiveData<List<CurrentTask>>
#Insert
suspend fun insertCurrentTask(currentTask: CurrentTask)
#Query("DELETE FROM current_task_table")
fun deleteAllCurrentTasks()
}
#Database(entities = [CurrentTask::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val currentTaskDao: CurrentTaskDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
if (instance == null) {
synchronized(this) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "app_database"
)
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
Fixed it by removing suspend from DAO
You cannot use suspend methods for DAO. Suspend function processed in compile time and compiler changes the signature of this function (different return type, an additional argument for state machine callback) to make it non-blocking.
Room waits for particular method signature to generate code. So, until Room doesn't support coroutines directly, you cannot use suspend function for DAO.
Found my answer here:
Error with Room dao class when using Kotlin coroutines
Removing the suspend is not necessary.
I solve this problem by this link.
Non-identification #Entity annotation
You need only have a small change in your build.gradle
please change this line
kapt "androidx.room:room-compiler:$room"
to this:
annotationProcessor "androidx.room:room-compiler:$room"
You can use suspend form in your DAO by this way

Room database setup in Android Library module

I have an existing Android project in which I have created a new Android Library (offlineservicelibrary)(File/new/New module/Android library). In this library I want to create a room database.
I followed https://developer.android.com/training/data-storage/room and created Entity (OfflineData) and DAO(OfflineDataDao) setup for my database, but I am a bit confused about where to setup AppDatabase and how Link it to the library.
#Database(entities = arrayOf(OfflineData::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getOfflineDataDao(): OfflineDataDao
}
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
In my Library I do not have any class which extends Application class. Should I be creating a new class which extends Application class and do the above steps there.
Please suggest on what I should be doing to get the Library setup with Room database please
This is my Project structure with Library
Make DatabaseManager Singleton class inside your module. Use #JvmStatic help you call from Java without Companion
class DatabaseManager private constructor(private val dp: AppDatabase) {
fun loadData() : List<OfflineData> {
return dp.getOfflineDataDao().loadAll() // I assume you already defined loadAll method in DAO
}
companion object {
#Volatile
private var INSTANCE: DatabaseManager? = null
#JvmStatic
fun getInstance(context: Context): DatabaseManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: run {
val db = Room.databaseBuilder(
context,
AppDatabase::class.java, "database-name"
).build()
DatabaseManager(db).also { INSTANCE = it }
}
}
}
}
}
In other modules you need to use your database, you have to implementation offlineservicelibrary in gradle and pass context to it when call
DatabaseManager.getInstance(context).loadData()

How to use createFromAsset function from Room library?

Room persistence library version 2.2.0-alpha01 has added the ability to use pre-packaged databases.
https://developer.android.com/jetpack/androidx/releases/room
Can someone provide an example for how to initialize the room database builder?
I'm using this :
#Database(entities = [Users::class], version = 1, exportSchema = false)
abstract class AppDataBase : RoomDatabase() {
companion object {
private const val DATABASE_NAME = "you_name"
private const val DATABASE_DIR = "database/you_name.db" // Asset/database/you_name.db
fun getInstance(context: Context): AppDataBase {
return Room
.databaseBuilder(context, AppDataBase::class.java, DATABASE_NAME)
.createFromAsset(DATABASE_DIR)
.build()
}
}
abstract fun getUsers(): UsersDao
}
for more information refer here
If you need update DB from Asset!
1. You need level up version Database in settings Room!
2. Add .fallbackToDestructiveMigration() method in getInstance
3. And need level up version in you db file;

Categories

Resources