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
Related
I am trying to use Database Inspector in Android Studio.
When I run the app on the device, inspector always showing my application database
Is there anything I need to set before using it?
I am not using db.closed()
And here is my database module code
#Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
//For singleton instantiation
#Volatile private var instance: AppDatabase?= null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.addCallback(
object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
}
).fallbackToDestructiveMigration()
.build()
}
}
}
Did you try some operation on DB e.g getlist
I have same issue when I am not in the page where DB is getting access.
Once I am in page, issue got resolved
Check the integrity of your database. You deleted an attribute, renamed a field, or deleted a model that is being referenced or imported in some class, when the room tries to load its data there is an integrity error and therefore it appears as closed.
Check your build and debug logs. I had the same problem and I solved it by the error logs and that was it.
I had the same issue, it is because in your actual session the connection is close... in other words you must execute one qwery on your app and the connection will be open.
Other option is create your create an instance on your main activity, this instance will open your database connection every time.
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.
According to documentation room instance from Room.databaseBuilder() should save data is persist. But still get lost. My Project have to database
First Database
#Database(entities = [FoodModel::class], version = 4, exportSchema = false)
abstract class FoodDatabase : RoomDatabase() {
abstract val foodDatabaseDao: FoodDatabaseDao
companion object {
#Volatile
private var INSTANCE: FoodDatabase? = null
fun getInstance(context: Context): FoodDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
FoodDatabase::class.java,
Constants.OVERVIEW_FOOD_DATABASE
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Second Databse
#Database(entities = [MyFoodModel::class], version = 3, exportSchema = false)
abstract class MyFoodDatabase : RoomDatabase() {
abstract val myFoodDatabaseDao: MyFoodDatabaseDao
companion object {
#Volatile
private var INSTANCE: MyFoodDatabase? = null
fun getInstance(context: Context): MyFoodDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
MyFoodDatabase::class.java,
Constants.OVERVIEW_FOOD_DATABASE
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Dao of first Database
#Dao
interface MyFoodDatabaseDao {
#Insert
fun insert(food: MyFoodModel)
#Query("SELECT * FROM MyFoodItems ORDER BY name DESC")
fun getAllFood(): LiveData<List<MyFoodModel>>
#Delete
fun deleteFood(foodModel: MyFoodModel)
}
Dao of Second database
#Dao
interface MyFoodDatabaseDao {
#Insert
fun insert(food: MyFoodModel)
#Query("SELECT * FROM MyFoodItems ORDER BY name DESC")
fun getAllFood(): LiveData<List<MyFoodModel>>
#Delete
fun deleteFood(foodModel: MyFoodModel)
}
An android application can have more than one database.
Here as I can see, You are providing same name [Constants.OVERVIEW_FOOD_DATABASE] to your both the databases [MyFoodDatabase, FoodDatabase]. So all values will be written in one database named as Constants.OVERVIEW_FOOD_DATABASE.
Please provide both the database different name and try again.
Edited
As you said, you are using two different instance of same databases and for every database instance, you are changing the database version but you are not migrating your database into that version. Instead you are using fallbackToDestructiveMigration() that does not crash database but clear the data when any existing version is found.
Please try below steps:
remove fallbackToDestructiveMigration() from both database instances.
in second instance add .addMigrations(MIGRATION_1_2) while creating
instance
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// do nothing because you are not altering any table
}
}
in First instance add .addMigrations(MIGRATION_2_1) while creating instance
val MIGRATION_2_1 = object : Migration(2, 1) {
override fun migrate(database: SupportSQLiteDatabase) {
// do nothing because you are not altering any table
}
}
It will migrate you same database. In my case it is working. I hope it will work in your case too. :)
But it is better to use single database instance and include the list of entities associated with the database within the annotation.
Because room database instances are expensive.
https://developer.android.com/training/data-storage/room
Note: If your app runs in a single process, you should follow the singleton design pattern when instantiating an AppDatabase object. Each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances within a single process.
If your app runs in multiple processes, include enableMultiInstanceInvalidation() in your database builder invocation. That way, when you have an instance of AppDatabase in each process, you can invalidate the shared database file in one process, and this invalidation automatically propagates to the instances of AppDatabase within other processes.
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()
I want to copy SQLite database from asset but it is not copying it is not throwing any Exception also
#Database(entities = [UserDetails::class, CircleMaster::class], version = 1, exportSchema = false
abstract class AppDatabase : RoomDatabase() {abstract fun getLoginDao(): LoginDao
abstract fun getRegisterDao(): RegisterDao
companion object {
#Volatile
private var instance: AppDatabase? = 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,
AppDatabase::class.java,
"Asset.db"
).createFromAsset("database/Asset.db").allowMainThreadQueries().build()
}}
Actually I got an answer from this link
Room: Database not created
when I tried to insert data that time database is copied from asset and data is inserted some people will face migration exception, I solved that by deleting room master table from the asset database.
Under the covers, by default, Room uses SQLiteOpenHelper, much as you might use it directly.
SQLiteOpenHelper does not create the database when you create the SQLiteOpenHelper instance. It will do so once you call getReadableDatabase() or getWriteableDatabase().
From a Room standpoint, that means until you perform some concrete operation, such as invoking a #Dao method that hits the database, your database will not be created.