Getting Room database reference inside appmodule - android

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.

Related

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()
}
}
}

Populate data from csv to room database kotlin

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

How to init and insert in Room database using Kotlin?

I'm new to kotlin and room, after following an official android guide i ended up by setting my entities, my DAO and my Database,
the issue is i can't understand on how can i use the function from dao in my fragment...
So my Database looks like this:
#Database(entities = [Articolo::class], version = 1, exportSchema = false)
abstract class ArticoliDatabase: RoomDatabase() {
abstract val articoliDao: ArticoliDAO
companion object {
#Volatile
private var INSTANCE: ArticoliDatabase? = null
fun getInstance(context: Context): ArticoliDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
ArticoliDatabase::class.java,
"pdt_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Then in my fragment i've done the following:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
db = ArticoliDatabase.getInstance(requireContext())
}
And in the same fragment in my click function i'm doing the following to insert
db.articoliDao.insert(Articolo(barcode, qta))
But the app even doesn't build correctly by saying that
ArticoliDatabase_Impl does not exist
So what is the right way to initialize and use the room database with kotlin?
I just need to simply insert and show the data from the db in a listview that's it..
The problem is in the build.gradle file. Can you please check if your import of room library looks like:
annotationProcessor "androidx.room:room-compiler:$room"
Since you are using kotlin, you must use kapt for annotation processor dependencies
This is how your room dependencies should look like
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
kapt "androidx.room:room-compiler:$room"
Create Database class AppDatabase and Dao Interface
AppDatabase
#Database(
entities = [User::class, Quote::class],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
abstract fun getQuoteDao(): QuoteDao
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,
"MyDatabase.db"
).build()
}
}
Dao Interface Here
#Dao
interface UserDao{
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(user: User) : Long
#Query("SELECT * FROM user WHERE uid = $CURRENT_USER_ID")
fun getuser() : LiveData<User>
}
Now in your fragment or activity you can insert and get user data by just craeting an object of dabase class as given below
private val db: AppDatabase
db=AppDatabase(context)
fun saveUser(user: User) = db.getUserDao().upsert(user)
fun getUser() = db.getUserDao().getuser()

Room databse loses data on restart application

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.

Dagger 2 get own Room instance

I want to add a callback to the room database to populate initial data.
#Provides
#Singleton
fun provideRoom(context: Context): MyRoomDatabase {
return Room.databaseBuilder(context, MyRoomDatabase::class.java, "my_database")
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
#Override
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
})
.build()
}
For that i need the database instance in the callback to access DAO for inserting data.
How does this work?
EDIT:
What I want to achieve:
Create initial data for the room database at the app installation
My Callback Class:
class RoomCallback(
var myRoomDatabase : MyRoomDatabase
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
myRoomDatabase.basicItemDao().insertList(
listOf(
BasicItem(),
BasicItem()
)
)
}
}
How i provide the RoomDatabase and the RoomCallback:
#Provides
#Singleton
fun provideRoom(context: Context, roomCallback: RoomCallback): MyRoomDatabase {
return Room.databaseBuilder(context, MyRoomDatabase::class.java, "my_database")
.fallbackToDestructiveMigration()
.addCallback(roomCallback)
.build()
}
#Provides
#Singleton
fun provideRoomCallback(myRoomDatabase: MyRoomDatabase): RoomCallback {
return RoomCallback(myRoomDatabase)
}
PROBLEM:
- The RoomCallback and RoomDatabase instance need both the other instance.
UPDATE: Using Kotlin Coroutine and Dagger2
Late in the party but for future readers, it's very easy to prepopulate your database at creation time or openning time. Make sure you have already added dependency in the gradle file for Coroutine. First create your database like:
/**
* Main database.
*/
#Database(
entities = [
Login::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun loginDao(): LoginDao
companion object {
#Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(app: Application): AppDatabase = INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(app).also { INSTANCE = it }
}
private fun buildDatabase(app: Application) =
Room.databaseBuilder(app,
AppDatabase::class.java,
"your database name")
.addCallback(object : Callback() {
// Pre-populate the database after onCreate has been called. If you want to prepopulate at opening time then override onOpen function instead of onCreate.
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Do database operations through coroutine or any background thread
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught during database creation --> $exception")
}
CoroutineScope(Dispatchers.IO).launch(handler) {
prePopulateAppDatabase(getInstance(app).loginDao())
}
}
})
.build()
suspend fun prePopulateAppDatabase(loginDao: LoginDao) {
val admin = Login(0, "Administrator", "1234", 1, isActive = true, isAdmin = true, isLogged = false)
loginDao.insertLoginData(admin)
}
}
}
Then you can provide singleton instance by placing below code to your dagger AppModule or in a separate database module as you wish.
#Singleton
#Provides
fun provideDb(app: Application): AppDatabase {
return AppDatabase.getInstance(app)
}
#Singleton
#Provides
fun provideLoginDao(db: AppDatabase): LoginDao {
return db.loginDao()
}
that's it, you are done. Inject anywhere your singleton database object like:
#Inject lateinit var loginDao: LoginDao
then use it.
Setup a database first
#Database(
entities = [User::class],
version = VERSION_CODE
)
abstract class DatabaseManager : RoomDatabase() {
abstract fun userDao(): UserDao
}
Now create a DatabaseModule
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideRoomDatabase(#ApplicationContext context: Context): RoomDatabase {
return Room.databaseBuilder(context, RoomDatabase::class.java, "dbName")
.setJournalMode(JournalMode.TRUNCATE)
.build()
}
}
You can create a separate module or add a method in DatabaseModule it self providing dao object. Say for example I have a UserDao then
#Module
class UserModule {
#Singleton
#Provides
fun provideDao(database: DatabaseManager): UserDao {
return database.userDao()
}
}

Categories

Resources