I'm using Android Room 2.3.0 and Dagger 2.
DBModule.kt that provides database instance looks like this:
#Singleton
#Provides
open fun provideDatabase(context: Context): AppDatabase {
return Room.databaseBuilder<AppDatabase>(
context.applicationContext, AppDatabase::class.java,
DATABASE_NAME
).fallbackToDestructiveMigration().build()
}
AppDatabase.kt class:
#Database(
entities = [User::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Now I need to add a few new columns into User entity and increase db version. How can I do a migration in AppDatabase.kt and call .addMigrations() if I don't have access to Room.databaseBuilder from AppDatabase.kt?
Just add the migrations to the DBModule.kt class, before calling .build().
Be careful with .fallbackToDestructiveMigration() though.
Related
I am trying to populate data from CSV to room database using kotlin in Android. I tried to convert the java code to Kotlin from this Populate Room database using csv file, but I am not able to access the Dao. Is there any better way to populate data from csv using Kotlin.
#Database(
entities = [MenuItem::class,
Order::class,
OrderItem::class,
Restaurant::class,
RestaurantRating::class,
User::class,
UserFavorite::class,
UserSearch::class],
version = 15,
exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
abstract val orderDao: OrderDao
abstract val restaurantDao: RestaurantDao
abstract val userDao: UserDao
companion object{
#Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"foodie_database.db"
)
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Executors.newSingleThreadExecutor().execute {
run {
}
}
}
})
.build()
INSTANCE = instance
}
return instance
}
}
}
}
From what I understand you are trying to pre-populate your database on first app startup. I didn't look into exactly how you can get access to the dao manually but the java example you pointed to I believe is using dagger dependency injection to get an instance of the dao.
How to overcome the circular dependency for a callback from Room.databaseBuilder using Dagger Hilt is wonderfully explained in a Code in flow video (link). I think this will give you enough information to figure out how to make the callback work.
Alternatively, and this is the one I prefer, you can use a database file instead of the csv file and use Room's inbuilt pre-population feature.
Just call .createFromAsset()orcreateFromFile() with Room.databaseBuilder()
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("myapp.db") //myapp.db in the assets folder
.build()
more from docs https://developer.android.com/training/data-storage/room/prepopulate#kotlin
I have an application where i need to prepopulate my database when it is created , i'm using dagger hilt to inject and provide dependencies ( room daos ) , when i try to insert data , it asks for movie database but don't know how to get its reference inside appmodule, thank you for any help in advance.
This is my database :
// this is my database
#Database(entities = [DataModel::class,MovieResultItem::class], version = 1, exportSchema = false)
abstract class MoviesDatabase : RoomDatabase() {
abstract fun popularDao() : PopularMoviesDao
}
Providing dao as dependency
#Singleton
#Provides
fun providePopularMoviesDao(moviesDatabase: MoviesDatabase) : PopularMoviesDao {
return moviesDatabase.popularDao()
}
Providing database instance
#Singleton
#Provides
fun provideDatabase(#ApplicationContext context: Context): MoviesDatabase {
return Room.databaseBuilder(context.applicationContext,
MoviesDatabase::class.java, "movie.db")
.addCallback(object : RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// here it asks for database instance , not sure how to get it
providePopularMoviesDao().insertPopularMovies(getMovieResultItem())
}
})
.fallbackToDestructiveMigration()
.build()
}
#Database(entities = [DataModel::class,MovieResultItem::class], version = 1, exportSchema = false)
abstract class MoviesDatabase : RoomDatabase() {
abstract fun popularDao(): PopularMoviesDao
#Volatile
private var INSTANCE: MoviesDatabase? = null
#Singleton
#Provides
fun provideDatabase(#ApplicationContext context: Context): MoviesDatabase {
return INSTANCE ?: synchronized(this) {
val databaseInstance = Room.databaseBuilder(
context.applicationContext,
MoviesDatabase::class.java, "movie.db"
)
.fallbackToDestructiveMigration()
.addCallback(InsertDatabaseCallback())
.build()
INSTANCE = databaseInstance
return instance
private class InsertDatabaseCallback() :RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let {
it.providePopularMoviesDao().insertPopularMovies(getMovieResultItem())
}
}
}
}
}
}
Would you please use the above code in your class MoviesDatabase.
Here I have added an INSTANCE variable for database and it is marked as Volatile.
Volatile means, it will not be stored in the local cache. There are lot of articles you can explore for more information on Volatile.
I have modified the provideDatabase function, so that the database instance is stored in variable INSTANCE
Then you can use this INSTANCE wherever required.
Also, I have separated the callback into class InsertDatabaseCallback for simpilicity and readability. Here you can use the database INSTANCE to perform your operation providePopularMoviesDao().insertPopularMovies(getMovieResultItem())
NOTE: The code may show syntax error or curly braces error, depending on your setup and imports. Do not worry there, please make slight changes OR add curly brackets (if required) and it should work fine.
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()
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;
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