Instance of abstract class in Kotlin and calling abstract function without overriding - android

I am doing this google codelab android-room-with-a-view-kotlin. This is the link to codelab. At the 8th step when creating room database they have used this code
// Annotates class to be a Room Database with a table (entity) of the Word class
#Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
// 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,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
I want to ask why there is no error in these 2 lines (val exam & val dao) where I create an instance of this abstract class (in class A) and then call its abstract function(getNoteDao) without overriding it.
class A{
val exam : WordRoomDatabase = WordRoomDatabase.getDatabase(application)
val dao = exam.getWordDao()
}
Since we know we need to override the abstract function and can not directly call it but what's happening there. why is there no error

There's no error on the exam line because getDataBase is a "companion" object, or if you're from the Java world, it means it's a "static" function within the abstract class. This means
a function within a companion object (or again, a static function) belongs the the CLASS, not the the INSTANCE of the class
you CAN NOT call a static/companion object function on the instance of the class, so notice when you call "WordRoomDatabase.getDatabase..." there are no parenthesis at the end of "WordRoomDatabase". You didn't need to create an instance of it in order to call the getDatabase function
There is no error in line 2 is a little trickier to spot.
Inside of getDatabase() you are Room.dataBaseBuilder(...) and passing in the abstract class. Inside of that builder, android actually creates the instance of your abstract WordRoomDatabase. class and overrides your abstract wordDao function
If you're using AndroidStudio, build your code. After it's done there will be a little green arrow pointing down on the column next to WordRoomDatabase. If you click on it, you'll be able to see the class that Room generated that overrides your abstract function

You don't get any error because implementation of those abstract classes is generated automatically by kapt at compile time. If you look closly at your build.gradle file, then you will see that it contains a dependency in the form
kapt 'androidx.room:room-compiler:X.X.X'
Here kapt stands for kotlin annotation processing tool, which processes all your Room classes marked with certain annotations such as #Database or #Dao and generates their implementations. For example, I defined following #Dao interface
#Dao
interface WordDao {
#Insert
fun insert(word: Word)
}
And kapt generated following implementation of this class
public final class WordDao_Impl implements WordDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<Word> __insertionAdapterOfWord;
public WordDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfWord = new EntityInsertionAdapter<Word>(__db) {
#Override
public String createQuery() {
return "INSERT OR ABORT INTO `Word` (`someId`) VALUES (?)";
}
#Override
public void bind(SupportSQLiteStatement stmt, Word value) {
stmt.bindLong(1, value.getSomeId());
}
};
}
#Override
public void insert(final Word word) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfWord.insert(word);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
}
Same goes for WordRoomDatabase, its implementation is also generated automatically. if you want to look at these classes you can always find them at \app\build\generated\source\kapt\debug\yourpackage, they are marked with _Impl suffix.

Related

How is it possible to call without implementing an abstract method in this Android's DB code?

I'm studying Android and I'm new to DB. I am trying to use the Room library to use the DB.
I'm looking at the sample code, but there's something I don't understand.
It is to call the abstract method of the AppDatabase abstract class without implementing it.
At least as far as I know, abstract classes cannot be instantiated.
But I'm curious how it can be called and how to use the returned value.
(Same for Kotlin.)
Am I wrong about JAVA or Kotlin?
ToDoDao.interface
#Dao // Data Access Object
interface ToDoDao {
#Query("SELECT * FROM ToDo")
abstract fun getAll(): List<ToDo>
#Insert
void insert(ToDo todo)
#Update
void update(ToDo todo)
#Delete
void delete(ToDo todo)
}
AppDatabase.class
#Database(entities = [Todo.class], version = 1)
abstract class AppDatabase extends RoomDatabase {
public abstract void TodoDao todotDao();
}
Main.class
AppDatabase db = Room.databaseBuilder(this, AppDatabase.class, "todo-db").build();
mResultTextView.setText(db.todoDao().getAll().toString); // THIS
We don't build database like this for simplicity , this and that are an example you can refer for building database and for accessing it use
val DB = AppDatabase.getInstance(context).ToDoDao ()

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.

error: Type of the parameter must be a class annotated with #Entity or a collection/array of it

I have looked at other posts regarding the same, but i don't follow what mistake i must be doing.
I have the same name declared in Entity for the class as well as in Database declaration file too.
I'm also passing the same type of parameter as the entity class name, and still i'm getting this error thrown and it is not compiling.
Here's my code. TIA
#Entity(tableName = "current_task_table")
data class CurrentTask (
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "task_name") val taskName: String
)
#Dao
interface CurrentTaskDao {
#Query("SELECT * FROM current_task_table")
fun getAllCurrentTask(): LiveData<List<CurrentTask>>
#Insert
suspend fun insertCurrentTask(currentTask: CurrentTask)
#Query("DELETE FROM current_task_table")
fun deleteAllCurrentTasks()
}
#Database(entities = [CurrentTask::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val currentTaskDao: CurrentTaskDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
if (instance == null) {
synchronized(this) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "app_database"
)
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
Fixed it by removing suspend from DAO
You cannot use suspend methods for DAO. Suspend function processed in compile time and compiler changes the signature of this function (different return type, an additional argument for state machine callback) to make it non-blocking.
Room waits for particular method signature to generate code. So, until Room doesn't support coroutines directly, you cannot use suspend function for DAO.
Found my answer here:
Error with Room dao class when using Kotlin coroutines
Removing the suspend is not necessary.
I solve this problem by this link.
Non-identification #Entity annotation
You need only have a small change in your build.gradle
please change this line
kapt "androidx.room:room-compiler:$room"
to this:
annotationProcessor "androidx.room:room-compiler:$room"
You can use suspend form in your DAO by this way

Android Room Database - LiveData - Update/Insert/Delete, skip observer (callback)

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.

How to add new tables in library with Room?

I am planning to start the migration of an existing app to Architecture Components and one of my doubts is how should I organize the new code.
I have some tables that are added in a personal library that's only included in some flavors, how can those Entities and DAOs be added to the main application if the database class exists on the main application?
Should I add another database class to the library? If so, wouldn't it collide with existing Database class in the main application?
I have been searching, but haven't been able to find any example or tutorial...
Edit to clarify Database question
From the docs I understand that in the Database abstract class, you have to tell wich Entities exists and also create access methods for the DAOs.
How could this be done if there are entities in the library?
#Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
#Database(version = 1, entities = {User.class, Book.class})
abstract class AppDatabase extends RoomDatabase {
// BookDao is a class annotated with #Dao.
abstract public BookDao bookDao();
// UserDao is a class annotated with #Dao.
abstract public UserDao userDao();
// UserBookDao is a class annotated with #Dao.
abstract public UserBookDao userBookDao();
}
If you want to update the Room database and add table...just add another entity then update the version if you add another entity like Movies table.. do something like this
#Database(version = 2, entities = {User.class, Book.class, Movies.class})
abstract class AppDatabase extends RoomDatabase {
// BookDao is a class annotated with #Dao.
abstract public BookDao bookDao();
// UserDao is a class annotated with #Dao.
abstract public UserDao userDao();
// UserBookDao is a class annotated with #Dao.
abstract public UserBookDao userBookDao();
// MoviesDao is a class annotated with #Dao.
abstract public MoviesDao moviesDao();
// UserMoviesDao is a class annotated with #Dao.
abstract public UserMoviesDao userMoviesDao();
}
for reference you can check ... this
The Room persistence library supports incremental migrations with the
Migration classes to address this need. Each Migration subclass
defines a migration path between a startVersion and an endVersion.
So, the right answer (and the correct way because you should not use fallbackToDestructiveMigration) is :
Add your new table as a java class
#Entity(tableName = "Fruit")
public class Fruit implements Parcelable {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(index = true, name = "id")
private int id;
#ColumnInfo(name = "name")
private String name;
...
}
Update your database version AND add your entity in entities declaration (you add the class definition to tells room should take the class into consideration) and add your dao getter
#Database(version = 2, entities = {User.class, Fruit.class})
abstract class AppDatabase extends RoomDatabase {
abstract public UserDao userDao();
abstract public FruitDao fruitDao();
}
Add migration sql script with your database builder like this
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2).build();
Source : https://developer.android.com/training/data-storage/room/migrating-db-versions
with Kotlin :
#Database(version = 1, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
//UserDao is a class annotated with #Dao
abstract fun userDao(): UserDao
// BookDao is a class annotated with #Dao
abstract fun bookDao(): BookDao
}
for a short cut.
to add a new table to room database.
1) create a new table
- you can do by creating a java class with annotation
#Entity(tableName = "user_data")
- inside this table you will create all the column/fields you want in your table.
2) create a new DAO (data access object)
- as we know we have a model class (java). we hold it like an object, to retrive,
so, create a interface with annotation
#Dao
- as this will have all the SQL query statement and act as a intermediate(interface) between the Database, and your commands.
3) adding your new table to the data base
- be carefull here, if you do it wrong, you may loose the data, or your app might crash.
1st add the table class you created to the entities attribute
#Database(entities = {TableOne.class,UserData.class},version = 1)
2nd note we don't increase the version number from 1 to 2, will explain you bellow you we did like this.
3rd add this abstract method, so that it can be overridden at every place as you need.
public abstract UserDataDAO getUserDataDao();
4th this would be same as you had before with the single table.
private static final String DB_Name = "myDatabase";
private static DataBase instance;
public static synchronized DataBase getInstance(Context context)
{
if(instance == null)
{
instance =
Room.databaseBuilder(context.getApplicationContext(),DataBase.class,DB_Name)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
5th after adding the new table your code will look like
#Database(entities = {TableOne.class,UserData.class},version = 1)
public abstract class DataBase extends RoomDatabase {
private static final String DB_Name = "myDatabase";
private static DataBase instance;
public abstract tableOne getTableOneDao();
public abstract UserDataDAO getUserDataDao();
public static synchronized DataBase getInstance(Context context)
{
if(instance == null)
{
instance =
Room.databaseBuilder(context.getApplicationContext(),DataBase.class,DB_Name)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
}
6th now uninstall the app, if you had already installed it in your device, since we did not migrate and change, that's why i not increased the version.
7th now when you install your app freshly it will be handling with two table.
8th by doing in this strategy is not appreciable, since your data is lost.
9th you need to migrate the app
change the version number by increasing, then write a static method which tells about the migration.
please look for this blog where you find awesome migration technique with clear explanation.
check here
With Kotlin you can use an array literal. Add your entities like in example code and higher database version:
Database(
entities = [Product::class, Category::class],
version = version + 1,
exportSchema = false)

Categories

Resources