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()
}
}
Related
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()
}
}
}
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'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()
I have a problem with injecting classes with Dagger2. I am using RoomDatabase for database access.
My room setup:
Dao's
interface noteDao()
interface noteTypeDao()
interface userDao()
NoteRepository
#Singleton
class NoteRepository #Inject constructor(
private val noteDao: NoteDao,
private val noteTypeDao: NoteTypeDao,
private val userDao: UserDao
) {
}
AppDatabase
#Database(entities = [Note::class, User::class, NoteType::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
abstract fun userDao(): UserDao
abstract fun noteTypeDao(): NoteTypeDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"NoteDatabase"
).build()
INSTANCE = instance
return instance
}
}
}
}
Dagger 2 setup:
AppModule
#Module
class AppModule {
#Provides
fun provideNoteRepository(app: Application): NoteRepository {
return NoteRepository(
AppDatabase.getDatabase(app).noteDao(),
AppDatabase.getDatabase(app).noteTypeDao(),
AppDatabase.getDatabase(app).userDao()
)
}
#Provides
fun provideApplication(): Application {
return Application()
}
}
AppComponent
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(app: MainActivity)
}
I am getting a NullPointerExeption int the AppDatabase in the line context.applicationContext. Any suggetion how to solve the problem?
It seems that the AppDatabase doesnt get the application instance from Dagger2.
Application is a framework class, you can not just instantiate it yourself by calling its constructor. Instead, you need to pass in your application that the framework instantiates for you into your module, and provide that:
#Module
class AppModule(val application: Application) {
...
#Provides
fun provideApplication(): Application {
return application
}
}
Now, if you were creating your AppComponent like this before, in your application's onCreate (presumably, as that's the usual way to do it):
override fun onCreate() {
injector = DaggerAppComponent.create()
}
You'd have to replace it with something like this, passing in your application instance to the module so that it can then provide it:
override fun onCreate() {
injector = DaggerAppComponent.builder()
.appModule(appModule(this))
.build()
}
I'm using Dagger 2 to create and share my RoomDatabase where necessary in my app.
I'm trying to implement addCallback() so I can override the database's onCreate() function and use it to insert my initial database values. This is where I'm running into issues.
I feel like I have to be overlooking something obvious, but I can't figure out a way to do this gracefully.
RoomDatabase class:
#Database(
entities = [Station::class],
version = 1,
exportSchema = false
)
abstract class TrainDB : RoomDatabase() {
abstract fun stationDao() : StationDao
}
DAO:
#Dao
abstract class StationDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(stations: Station)
#Query("SELECT * FROM station_table")
abstract fun getAll() : LiveData<List<Station>>
}
Dagger Module:
#Module
class DataModule {
#Singleton
#Provides
fun provideDb(app: Application): TrainDB {
var trainDB: TrainDB? = null
trainDB = Room
.databaseBuilder(app, TrainDB::class.java, "train.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
/*
WHAT GOES HERE?
*/
}
})
.build()
return trainDB
}
#Singleton
#Provides
fun providesStationDao(db: TrainDB) : StationDao = db.stationDao()
}
I'd like to be able to access my DAO in the onCreate() callback. It seems obvious that this should be possible because Google is pushing Room and Dagger together and this is likely a pretty common use case.
I've tried providing the DAO as a constructor argument for provideDB(), but that creates a circular dependency
I've tried initializing my RoomDatabase as a companion object. Then instead of using the Room.builder format in my provideDB() method, I can call a getInstance() method that has access to the DAO. But this way I'm met with an error for a recursive call to getWriteableDatabase().
I understand that I can use something like db.execSQL(), but it seems like such a shame to do that when I'm using Room.
Is there a better way that I'm missing? I'm using Kotlin, but Java examples are welcome. :)
I have managed it like this:
#Module
class DataModule {
lateinit var trainDB: TrainDB
#Singleton
#Provides
fun provideDb(app: Application): TrainDB {
trainDB = Room
.databaseBuilder(app, TrainDB::class.java, "train.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
/*
trainDB.stationDao().insert(...)
*/
}
})
.build()
return trainDB
}
#Singleton
#Provides
fun providesStationDao(db: TrainDB) : StationDao = db.stationDao()
}
But remember You need to to do a fake read from the database in order to initiate the db and invoke onCreate(). don't write into db as your first interact when db hasn't been created because it will create a race condition and your on create writing won't take effect.
You can create final one-element array.
#AppScope
#Provides
public AppDatabase appDatabase(#ApplicationContext Context appContext, AppExecutors executors) {
final AppDatabase[] databases = new AppDatabase[1];
databases[0] = Room.databaseBuilder(appContext, AppDatabase.class, AppDatabase.DATABASE_NAME)
.fallbackToDestructiveMigration()
.addCallback(new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
executors.diskIO().execute(() -> {
databases[0].populateDatabaseOnCreate();
});
}
}).build();
return databases[0];
}
We can inject lazily to prevent circular dependency.
For example
#Singleton
#Provides
fun provideDb(
app: Application,
trainDBLazy: Lazy<TrainDB> // import dagger.Lazy
): TrainDB {
return Room
.databaseBuilder(app, TrainDB::class.java, "train.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
var trainDB = trainDBLazy.get()
}
})
.build()
}
You can do this in java
AppDatabase appDatabase = null;
AppDatabase finalAppDatabase = appDatabase;
appDatabase = Room.databaseBuilder(MyApplication.getApplication(),
AppDatabase.class, Constants.DATABASE_NAME).
addCallback(new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
//check for null
finalAppDatabase.yourDao();
}
}).
build();
return appDatabase;