I am writing my first Android application in Kotlin.
I was wondering how to instantiate a Room Persistence database so that I can access it from many different activities.
Sorry if my question is dumb and not too specific, but I'm not sure what details may be useful.
Try this way..
make app level activity like below for database creation.
class AppActivity:Application() {
companion object {
var db: AppDatabase? = null
fun getDatabase(): AppDatabase? {
return db
}
}
override fun onCreate() {
super.onCreate()
db= Room.databaseBuilder(applicationContext, AppDatabase::class.java,"DB").allowMainThreadQueries().build()
}
}
after that this activity define in manifest in application tag..
android:name=".app.AppActivity"
after that make dao class..
#Dao
interface TodoDao {
#Query("SELECT * FROM Todo")
fun getTodoData()= mutableListOf<Todo>()
#Insert
fun insertTodo(todo: Todo)
#Update
fun updateTodo(todo: Todo)
#Delete
fun deleteTodo(todo: Todo)
}
make database class for access all dao class..
#Database(entities = arrayOf(User::class,Assignment::class,Todo::class, Student::class,Event::class,Comment::class,Feedback::class ,Achivement::class,Note::class, Syllabus::class, Education::class, Mark::class, UserWork::class, Exam::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getUser(): UserDao
abstract fun getStudent(): StudentDao
abstract fun getEducationDao(): EducationDao
abstract fun getWorkDetails(): UserWorkDao
abstract fun getExamDao(): ExamDao
abstract fun getMarkDao(): MarkDao
abstract fun getSyllabusDao(): SyllabusDao
abstract fun getNoteDao(): NoteDao
abstract fun getAchivement():AchivementDao
abstract fun getFeedbackDao():FeedbackDao
abstract fun getCommentDao():CommentDao
abstract fun getEventDao():EventDao
abstract fun getAssignDao():AssignmentDao
abstract fun getTodoDao():TodoDao
}
make table for todo..
#Entity
class Todo {
#PrimaryKey(autoGenerate = true)
var id:Int=0
var time:String=""
var activityName:String=""
var hours:String=""
var types:String=""
}
after in activity or fragment insert data like below..
var data=Todo().apply {
time=mSpTime?.selectedItem.toString()
types=mSpTypes?.selectedItem.toString()
activityName=mEtAname?.text.toString().trim()
hours=mEtHour?.text.toString().trim()
}
AppActivity.getDatabase()?.getTodoDao()?.insertTodo(data)
You can create instance using companion objectlike this,
#Database(entities = arrayOf(UserEntity::class), version = 1)
abstract class UserDb : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var INSTANCE: UserDb? = null
fun getInstance(context: Context): UserDb? {
if (INSTANCE == null) {
synchronized(UserDb::class) {
INSTANCE = Room.databaseBuilder(context.applicationContext, UserDb::class.java, "user.db").build()
}
}
return INSTANCE
}
fun destroyInstance() {
INSTANCE = null
}
}
}
You can get instance of db in your activity like this,
UserDb.getInstance(this)
Alternatively, you can also use dependency injection libraries like Dagger
Check the documentation in this link, here you will find detailed step by step tutorial about setting up Room.
The correct way to have an instance of the DB is:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
Remember you have to use one more dependency for kotlin and room
// Room
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"
apply plugin: "kotlin-android-extensions"
Related
In my project, I use the Singleton design pattern, add a companion object and a function that returns a database object instance. This will avoid creating multiple instances
database object through which the connection to the SQL server is established.
I have the following code to connect to the database:
// Annotates class to be a Room Database with a table (entity) of the Word class
#Database(entities = arrayOf(ShoppingList::class), version = 1, exportSchema = false)
public abstract class ShoppingListRoomDatabase : RoomDatabase() {
abstract fun shoppingListDao(): ShoppingListDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
public var INSTANCE: ShoppingListRoomDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): ShoppingListRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ShoppingListRoomDatabase::class.java,
"shopping_list_database"
).addCallback(ShoppingListDatabaseCallback(scope)).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
private class ShoppingListDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
ShoppingListRoomDatabase.INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.shoppingListDao())
}
}
}
fun populateDatabase(shoppingListDao: ShoppingListDao) {
shoppingListDao.deleteAll()
var shoppingList = ShoppingList(1,"First List")
shoppingListDao.insert(shoppingList)
shoppingList = ShoppingList(2, "Second List!")
shoppingListDao.insert(shoppingList)
}
}
Interface:
#Dao
interface ShoppingListDao {
#Query("SELECT * FROM shopping_lists ORDER BY id ASC")
fun getOrderedShoppingLists(): Flow<List<ShoppingList>>
#Insert
fun insert(shoppingList: ShoppingList)
#Query("DELETE FROM shopping_lists")
fun deleteAll()
}
How can I get this database instance in another kotlin class to work with it?
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 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()
}
}