I'm currently trying to set up some initial data in the Room database.
As a result, the initial data setup was successful, but App Inspection confirmed that the data is saved only when getWorkoutList().
To explain in more detail, the initial data is not saved with the insert function alone, and the initial data is saved in the DB only when a function that calls the DB data called getWorkoutList() is executed from the ViewModel.
When the database is created in the view model, I expected the initial data to be saved only with the insert function. But it wasn't.
Why is the initial data not saved with only the insert function?
The following is the DB status in App inspection according to the function call.
1. When only insertWorkoutList(data) is executed
There is no DB and table creation, and no initial data is saved.
2. When only getWokroutList() is executed.
DB and table are created, but there is no data.
3. When both are executed.
Initial data is normally saved.
Code
Dao
#Dao
interface WorkoutListDao {
#Query("SELECT * FROM WorkoutList")
suspend fun getWorkoutList() : WorkoutList
#Insert
suspend fun insertWorkoutList(workoutList: WorkoutList)
}
WorkoutListDatabase
#Database(
entities = [WorkoutList::class],
version = 1
)
#TypeConverters(WorkoutListTypeConverter::class)
abstract class WorkoutListDatabase : RoomDatabase() {
abstract fun workoutListDao() : WorkoutListDao
companion object {
private var INSTANCE : WorkoutListDatabase? = null
#Synchronized
fun getDatabase(context: Context) : WorkoutListDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WorkoutListDatabase::class.java,
"workoutlist_db"
)
.addCallback(WorkoutListCallback(context))
.build()
INSTANCE = instance
instance
}
}
}
}
WorkoutListCallback
class WorkoutListCallback(private val context: Context) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
fillWithStartingWorkoutList(context)
}
}
private fun fillWithStartingWorkoutList(context: Context) {
val dao = WorkoutListDatabase.getDatabase(context).workoutListDao()
try {
val data = loadJsonData(context)
// dao.insertWorkoutList(data)
} catch (e: JSONException) {
e.printStackTrace()
}
}
private fun loadJsonData(context: Context) : WorkoutList {
val assetManager = context.assets
val inputStream = assetManager.open(WORKOUTLIST_JSON_FILE)
BufferedReader(inputStream.reader()).use { reader ->
val gson = Gson()
return gson.fromJson(reader, WorkoutList::class.java)
}
}
}
ViewModel
class WorkoutListViewModel(application: Application) : AndroidViewModel(application) {
private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao()
private val workoutListRepo = WorkoutListRepository(workoutDao)
fun setList(part : BodyPart) {
viewModelScope.launch(Dispatchers.IO) {
workoutListRepo.getWorkoutList()
}
}
}
The callback will only be called when the database is actually accessed NOT when an instance of the #Database annotated class is obtained.
As such the database is not created by:-
val dao = WorkoutListDatabase.getDatabase(context).workoutListDao()
but is created by
workoutListRepo.getWorkoutList()
That is the actual relatively resource hungry action of opening the database is left until it is definitely needed.
A get around could be to use :-
fun getDatabase(context: Context) : WorkoutListDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WorkoutListDatabase::class.java,
"workoutlist_db"
)
.addCallback(WorkoutListCallback(context))
.build()
INSTANCE = instance
instance.getOpenHelper().getWritableDatabase() //<<<<< FORCE OPEN
instance
}
}
This would then force an open of the database and if it does not actually exist then the overidden onCreate method will be invoked. However,this (I think) would be done on the main thread, which you do not want.
I would strongly suggest NOT using functions from the #Dao annotated class(es) especially if they have suspend as you then may have no control over when the threads will run.
Instead you should use the SupportSQLiteDatabase passed to the function, it has many methods e.g. you would likely use the insert method.
see ROOM database: insert static data before other CRUD operations for an example where the order was an issue. The example includes the conversion of the actions to utilise the intended SupportSQLiteDatabase methods.
You may notice that the example includes placing all the database changes into a single transaction, which is more efficient as instead of each individual action (insert/delete in the example) writing to disk, the entire transaction (all actions) are written to disk once.
Related
I'm relatively new to Kotlin and I'm working on a project for school. I've gotten stuck on something I can't figure out for a couple days now, either because I'm not just understanding how it works or I just don't know what to search for. I'm building an app for simple budget tracking, and using Room DB to allow the user to enter and store their expenses. I've gotten most of the app built and working, and have the DB, a DAO, a Repository and a ViewModel. I've successfully written a Query that returns the sum through a LiveData<Double>. I've managed to get this sum value to display through both a Toast message and in a TextView in the MainActivity (but the TV doesn't update on load, only after launching the activity for modifying the DB entries for the first time).
If it's possible, I want to be able to take this sum and store it inside a separate class I've written for calculation functions, and have it update whenever a user enters or deletes something from the DB. Or preferably, have the non-activity class call this sum whenever the class's relevant functions are called. I don't seem to understand how to get this value from anywhere but the MainActivity. Everything I've searched and read has sections of code which I think I understand, such as observeForever which require an application parameter, or they're over my head because it's just code snippets which I can't wrap my head around how they fit together.
Here is what I have so far:
My Entity:
#Entity(tableName = "expenses")
data class Expenses (
#PrimaryKey(autoGenerate = true)
val id: Int,
val expDesc: String,
val expAmount: Double
)
My DAO:
#Dao
interface Dao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addData(expenses: Expenses)
#Query("SELECT * FROM expenses ORDER BY id ASC")
fun readAllData(): LiveData<List<Expenses>>
#Query("SELECT SUM(expAmount) as expenseSum FROM expenses")
fun getExpenseSum(): LiveData<Double>
#Update
suspend fun updateExpense(expenses: Expenses)
#Delete
suspend fun deleteData(expenses: Expenses)
#Query("DELETE FROM expenses")
suspend fun deleteAllData()
}
My Database:
#Database(entities = [Expenses::class], version = 1, exportSchema = false)
abstract class Database:RoomDatabase() {
abstract fun dao(): Dao
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): Dao?
}
companion object{
#Volatile
private var INSTANCE: com.example.finalproject.roomDB.Database? = null
fun getDatabase(context: Context): com.example.finalproject.roomDB.Database {
val instance = INSTANCE
if(instance != null){
return instance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
com.example.finalproject.roomDB.Database::class.java,
"expenses").build()
INSTANCE = instance
return instance
}
}
}
}
My Repository:
class Repository(private val dao: Dao) {
val readAllData: LiveData<List<Expenses>> = dao.readAllData()
val getExpenseSum: LiveData<Double> = dao.getExpenseSum()
suspend fun addData(expenses: Expenses){
dao.addData(expenses)
}
suspend fun updateData(expenses: Expenses){
dao.updateExpense(expenses)
}
suspend fun deleteData(expenses: Expenses){
dao.deleteData(expenses)
}
suspend fun deleteAllData(){
dao.deleteAllData()
}
}
My ViewModel:
class ViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Expenses>>
val getExpenseSum: LiveData<Double>
private val repository: Repository
init{
val dao = Database.getDatabase(application).dao()
repository = Repository(dao)
readAllData = repository.readAllData
getExpenseSum = repository.getExpenseSum
}
fun addData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) {
repository.addData(expenses)
}
}
fun updateData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) { repository.updateData(expenses) }
}
fun deleteData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) { repository.deleteData(expenses) }
}
fun deleteAllData(){
viewModelScope.launch(Dispatchers.IO) { repository.deleteAllData() }
}
}
My currently relevant part of the MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ViewModel
var sumTotal: Double = 0.0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val expenseViewButton = findViewById<Button>(R.id.expViewButton)
val incomeViewButton = findViewById<Button>(R.id.incViewButton)
val goalsViewButton = findViewById<Button>(R.id.goalsViewButton)
val expectedExpAmtView = findViewById<TextView>(R.id.expectedExpAmt)
viewModel = ViewModelProvider.AndroidViewModelFactory(application).create(ViewModel::class.java)
//This observer successfully casts the LiveData<Double> to a Double and updates whenever changed
val sumObserver = Observer<Double> { expSumDbl -> sumTotal = expSumDbl }
viewModel.getExpenseSum.observe(this, sumObserver)
expectedExpAmtView.text = getString(R.string.monthly_expected_ExpAmt, sumTotal.toString())
expenseViewButton.setOnClickListener{
val myIntent: Intent = Intent(this, ExpenseActivity::class.java)
startActivity(myIntent)
}
incomeViewButton.setOnClickListener{
val myIntent: Intent = Intent(this, IncomeActivity::class.java)
startActivity(myIntent)
}
//This successfully displays the correct sum whenever the button is pressed
goalsViewButton.setOnClickListener{
Toast.makeText(this#MainActivity, sumTotal.toString(), Toast.LENGTH_SHORT).show()
}
}
}
So sumTotal is the value from MainActivity that I'd like to get in a different non-activity class* for calculations that won't affect the DB at all, only text views. I'd also like the TextView that is being updated to always be up-to-date, including when the app launches. If anyone can help me figure out what I am doing wrong and/or need to do differently I'd really appreciate it.
*Specifically at the moment, I have a budget class which handles getting things like income, entered into editText fields, and calculating how much that translates into on a monthly basis. I'd like to take the sum from the DB entries and subtract it from whatever that total becomes in the budget class and return the result. I might want to do other (undecided) things later with the sum, which is why I want to store it in a variable.
The value of sumTotal in MainActivity actually comes from Repository.getExpenseSum, so instead of sharing that variable to another class, it might be easier to call the repository method again from the other class. Actually, I wouldn't even recommend having that variable sumTotal in your Activity, it's better to rely on ViewModel only, but that's a different topic.
You interface Dao contains the method:
fun getExpenseSum(): LiveData<Double>
which returns a LiveData. If you want to observe a LiveData, you need to have a LifecycleOwner so the LiveData knows when it should start and stop emitting updates (or you can observeForever, but you need to know yourself when to stop observing it).
In your budget class, assuming it's not a Fragment/Activity, you won't have a LifecycleOwner, that's why one suggestion for you issue is to create another method in Dao:
fun getExpenseSum(): Double
Notice the lack of LiveData. That method will return a double whenever you call it, synchronously, but it needs to be executed on a background thread. You can call that method in your budget class and get that value there.
Lastly, I don't think you should be calling those DB methods in some "regular" classes, you should pass those variables when creating an instance of the budget class. It's much easier to deal with LiveData/background thread when you're on the standard Android classes, and just pass the values to other classes that need it, instead of making them query the repository themselves.
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.
Disclaimer: This is not actual code from any app, but an example of the flow and my current understanding on how best to do this. I am looking for help improving upon or what I am doing wrong.
I am trying to figure out the best way to structure an android application using the new jetpack viewModels, realm, and coroutines. I put together a gist of the flow that I have so far, and would love some feedback on how I can improve, what I could change, or what I am doing wrong. Ideally with examples or direct changes to my code.
It works as is, I am just not sure if I am using coroutines correctly or efficiently, and if there is a better way to structure the DAO's so that Realm can be injected for better testability. Someone has already mentioned changing the DAO to extend the LiveData<>, and using onActive() and onInactive() for posting the object. Is that a good idea?
// About Model is the model used by Realm. These models contains realm specific types, like RealmList
open class AboutModel(
var name: String = "",
#PrimaryKey
var version: String = ""
): RealmObject() {
/**
* Conversion function, to convert the view model layer object to the data layer object
*/
companion object {
fun from(about: About): AboutModel = AboutModel(about.name, about.version)
}
fun toObject(): About =
About(
this.name,
this.version
)
}
// About class used everywhere outside of the data/realm layer.
// Lines up with the AboutModel class, but free of realm or any other database specific types.
// This way, realm objects are not being referenced anywhere else. In case I ever need to
// replace realm for something else.
class About (val name: String = "Test", val version: String = "1.0.0") {
override fun toString(): String {
return "author is : $name, version is: $version"
}
}
// Couldn't inject the realm instance because its thread would not match with a suspend function.
// Even if both where background threads. Would be better if I could inject it, but couldn't get
// that to work.
class AboutDao() {
private val _about = MutableLiveData<About>()
init {
val realm = Realm.getDefaultInstance()
val aboutModel = realm.where(AboutModel::class.java).findFirst()
_about.postValue(aboutModel?.toObject() ?: About())
realm.close()
}
suspend fun setAbout(about: About) = withContext(Dispatchers.IO) {
val realm: Realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.copyToRealmOrUpdate(AboutModel.from(about))
_about.postValue(about)
}
realm.close()
}
fun getAbout() = _about as LiveData<About>
}
// Database is a singleton instance, so there is only ever one instance of the DAO classes
class Database private constructor() {
var aboutDao = AboutDao()
private set
companion object {
// #Volatile - Writes to this property are immediately visible to other threads
#Volatile private var instance: Database? = null
suspend fun getInstance() = withContext(Dispatchers.IO) {
return#withContext instance ?: synchronized(this) {
instance ?: Database().also { instance = it }
}
}
}
}
// Repo maintains the dao access. Is also setup to run as a singleton
class AboutRepo private constructor(private val aboutDao: AboutDao){
// This may seem redundant.
// Imagine a code which also updates and checks the backend.
suspend fun set(about: About) {
aboutDao.setAbout(about)
}
suspend fun getAbout() = aboutDao.getAbout()
companion object {
// Singleton instantiation you already know and love
#Volatile private var instance: AboutRepo? = null
fun getInstance(aboutDao: AboutDao) =
instance ?: synchronized(this) {
instance ?: AboutRepo(aboutDao).also { instance = it }
}
}
}
// Injector is used to help keep the injection in a single place for the fragments and activities.
object Injector {
// This will be called from About Fragment
suspend fun provideAboutViewModelFactory(): AboutViewModelFactory = withContext(Dispatchers.Default) {
AboutViewModelFactory(getAboutRepo())
}
private suspend fun getAboutRepo() = withContext(Dispatchers.IO) {
AboutRepo.getInstance(Database.getInstance().aboutDao)
}
}
// AboutViewModel's Factory. I found this code online, as a helper for injecting into the viewModel's factory.
class AboutViewModelFactory (private val aboutRepo: AboutRepo)
: ViewModelProvider.NewInstanceFactory() {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return AboutViewModel(aboutRepo) as T
}
}
// About Fragments ViewModel
class AboutViewModel(private val aboutRepo: AboutRepo) : ViewModel() {
suspend fun getAbout() = aboutRepo.getAbout()
suspend fun setAbout(about: About) = aboutRepo.set(about)
}
// Fragment's onActivityCreated, I set the viewModel and observe the model from the view model for changes
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch {
viewModel = ViewModelProviders.of(
this#AboutFragment,
Injector.provideAboutViewModelFactory()
).get(AboutViewModel::class.java)
withContext(Dispatchers.Main) {
viewModel.getAbout().observe(viewLifecycleOwner, Observer { about ->
version_number.text = about?.version
})
}
}
}
I'm new in Room Database, and recently I faced a problem that has to do with modifying the App's database (update/insert/delete) without causing the callback in observer to be fired.
This is the Dao class for my model:
#Dao
interface ReceiptRowDao {
#Query("SELECT * FROM my_model ")
fun getMyModels(): LiveData<MutableList<MyModel>>
#Update
fun update(receiptRow: ReceiptRow): Int
}
My Database class:
#Database(
entities = [
(MyModel::class)
],
version = 1,
exportSchema = false
)
abstract class AppDatabase: RoomDatabase() {
abstract fun myModelDao(): MyModelDao
}
Usage:
class MyClass {
val db: AppDatabase = Room
.databaseBuilder(mContext, AppDatabase::class.java, "my_db")
.allowMainThreadQueries()
.build()
fun test() {
val myLiveData = db.myModelDao.getMyModels()
myLiveData!!.observe(this, Observer { data ->
...
val item = data.get(0)
item.id = 4
// This update should not cause a callback
db.myModelDao().update(item)
...
})
}
}
In the MyClass, the update instruction will cause an infinite loop, since an update to MyModel, will fire the observer. Then the code inside the observer will run again. This will do another update. This will fire the observer again and so on...
In such a scenario, is there a way to do the update of a model, but to skip the observers that might be listening for changes?
I think what you could do is just simply check whether data is already in the database. Like
fun test() {
val myLiveData = db.myModelDao.getMyModels()
myLiveData!!.observe(this, Observer { data ->
...
val item = data.get(0);
// This update should not cause a callback
if (!db.myModelDao().itemExists(item){
db.myModelDao().update(item)
}
...
})
}
this is the database class which holds the DAO classes along with its instance
#Database(entities = {Weight.class, DailyConsumption.class, DrinkType.class}, version = 1, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {
// DAO classes
public abstract WeightDao weightDao();
public abstract DailyConsumptionDao dailyConsumptionDao();
public abstract DrinkTypeDao drinkTypeDao();
private static MyDataBase dataBase;
public static MyDataBase getInstance(Context context){
if (null== dataBase){
dataBase= buildDatabaseInstance(context);
}
return dataBase;
}
private static MyDataBase buildDatabaseInstance(Context context) {
return Room.databaseBuilder(context,
MyDataBase.class,
Constants.DB_NAME)
.allowMainThreadQueries().build();
}
}
and the part where you want to insert the data in database takes two parameters. one database class object and the entities. I have designed the entities classes like a model class which you can use to set and get values inside main class.
dailyConsumption = new DailyConsumption(); // entity class
myDataBase = MyDataBase.getInstance(this);
dailyConsumption.setIcon(mIcon);
dailyConsumption.setQuantity(mQuantity);
dailyConsumption.setTime(strTime);
dailyConsumption.setIsSelected(0);
dailyConsumption.setDrinkUnit(drinkUnit);
dailyConsumption.setDateTime(insertionDate);
dailyConsumption.setDate(date);
setDailyDataBase(myDataBase, dailyConsumption);
and the method setDailyDatabase just calls the database class to insert the entity
private void setDailyDataBase(MyDataBase dataBase, DailyConsumption dailyConsumption) {
// query takes parameters to update respective columns
myDataBase.dailyConsumptionDao().updateItem(mId, mQuanity, mTime, date);
}
For your issue, i would suggest you following way for observing LiveData & updation of your Model:
class MyClass {
val db: AppDatabase = Room
.databaseBuilder(mContext, AppDatabase::class.java, "my_db")
.allowMainThreadQueries()
.build()
fun getDataObserver() = db.myModelDao.getMyModels()
fun test(item: MyModel) {
db.myModelDao().update(item)
}
}
This will help you seperate your observer logic from update logic, now call getDataObserver() method where you want to observe data and use your test() method when you want to update your Model.
Whether it's an insert operation in table X or a delete one from table Y, the issue is that only one operation is being executed and for it to work again I need to re-run my app, making a new APK installation. That is, if I insert in table X and attempt to make another insert in table Y, although it will list in the app, it will not persist in the database.
I have confirmed it by running my application in debug mode and inspecting the database using the com.amitshekhar.android:debug-db:1.0.4. It is this weird, listing from the database while not being in the database. Then, when running the app one more time, these "ghost data" disappear.
I don't know what to show except for the operations code. The easiest one, a Student insertion:
fun onClickSaveStudentButton(view: View?) {
val studentName: String = mEdtStudentName?.text.toString()
val studentEntry = Student(null, studentName)
thread { mDb?.studentsDao()?.insert(studentEntry) }
finish()
}
Only one field for the name, and a thread executing the insertion. It sometimes will allow me to insert twice but after it, the same problem will happen. I thought it could be something with thread, so I installed Anko and made use of doAsync, but the problem continues.
This is the DAO function to insert:
#Insert
fun insert(data: Student)
What is going wrong here? Why's it only allowing me to insert once or twice?
The database class:
#Database(entities = [(Schedule::class), (Student::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
companion object {
#Volatile
private var sInstance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase = sInstance ?: synchronized(this) {
sInstance ?: Room.databaseBuilder(
context,
AppDatabase::class.java,
Constants.DATABASE_NAME
).build().also {
sInstance = it
}
}
}
abstract fun scheduleDao(): ScheduleDao
abstract fun studentsDao(): StudentDao
}
And here, as example, Student model:
#Entity(tableName = "students")
class Student constructor(
#PrimaryKey(autoGenerate = true) var id: Int?,
#ColumnInfo(name = "name") var name: String?
)
Full code here.