Ignoring Particular Fields Using Room on Android - android

Having Room used in Android app, I created entity objects as required. Also, having decoupled layered architecture, I use Room entities only in data layer, converting them further to domain objects to be used in use case and UI layers.
Now, the problem is following:
Room provides us with very convinient way to to CRUD operations with simple anotating methods and providing Entity object.
But, how can we achieve that when Entities (and DB tables) have some meta data fields that are used only in db layer, and I don't map them to domain objects.
Here is an example of two simple classes from different layers (without annotations, for convenience):
data class CarDbEntity(id: Int, brand: String, color: String, _createdAt: Long, _someOtherMeta: Int)
data class CarDomainObject(id: Int, brand: String, color: String)
Now, I have convertors from DbEntity to DomainObject classes. But it cannot be done vice versa, since I am not mapping _metaFields to DomainObjects. Which means, since I cannot properly recreate DbEntity class, I cannot use convinient ways of crudding like this:
#Update
fun updateCar(car: CarDbEntity)
So, if I want to update car brand name or color, I need to write manually custom #Query with SQL for each case I need it.
I even tried creating CarWithNoMetaDbEntity trying to attach it to the same table as CarDbEntity, hoping that it would update just fields that exist in that class (and leave _meta as it is) but Room doesn't allow multiple entity classes bound to the same table.
Is there any technique where I can achieve updating such objects easily, without writing bunch of update SQL queries?

Related

(Anrdoid) How to prepopulate the Room database

I made a screen like the current image.
Data such as A, B, C.. are currently being set by getting from the strings.xml resource file.
I am now going to use Room DB instead of strings.xml and I want to get these data from Room.
To do this, we need to pre-populate the Room with data.
In the sample code I found, the method called addCallback() was usually used.
like this :
#Database(entities = arrayOf(Data::class), version = 1)
abstract class DataDatabase : RoomDatabase() {
abstract fun dataDao(): DataDao
companion object {
#Volatile private var INSTANCE: DataDatabase? = null
fun getInstance(context: Context): DataDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
DataDatabase::class.java, "Sample.db")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// insert the data on the IO Thread
ioThread {
getInstance(context).dataDao().insertData(PREPOPULATE_DATA)
}
}
})
.build()
val PREPOPULATE_DATA = listOf(Data("1", "val"), Data("2", "val 2"))
}
}
However, as you can see from the code, in the end, data (here, val PREPOPULATE_DATA) is being created again within the code. (In another code, db.execSQL() is used)
In this way, there is no difference from fetching data from resource file in the end.
Is there any good way?
Developer documentation uses assets and files.
However, it is said that it is not supported within In-memory Room databases.
In this case, I do not know what In-memory means, so I am not using it.
In this case, I do not know what In-memory means, so I am not using it.
In-Memory will be a database that is not persistent, that is the database is created using in memory rather than as a file, at some time it will be deleted. You probably do not want an in-memory database.
However, as you can see from the code, in the end, data (here, val PREPOPULATE_DATA) is being created again within the code. (In another code, db.execSQL() is used)
This is a common misconception when writing Apps as the onCreate method of an activity is often repeated when an App is running. With an SQLite database the database is created once in it's lifetime, which would be from the very first time the App is run until the database file is deleted. The database will otherwise remain (even between App version changes).
Is there any good way?
You basically have two options for a pre-populated database. They are
to add the data when/after the database is created, as in your example code (which is not a good example as explained below), or
to utilise a pre-packaged database, that is a database that is created outside of the App (typically using an SQlite tool such as DBeaver, Navicat for SQlite, SQLiteStudio, DB Browser for SQLite).
Option 1 -Adding data
If the data should only be added once then using the overridden onCreate method via the CallBack can be used. However, using functions/methods from the #Dao annotated class(es) should not be used. Instead only SupportSQLiteDatabase functions/methods should be used e.g. execSQL (hence why the SupportSQLiteDatabase is passed to onCreate).
This is because at that stage the database has just been created and all the underlying processing has not been completed.
You could protect against duplicating data quite easily by using INSERT OR IGNORE .... rather than INSERT ..... This will skip insertion if there is an applicable constraint violation (rule being broken). As such it relies upon such rules being in force.
The two most commonly used constraints are NOT NULL and UNIQUE, the latter implicitly for a primary key.
In your case if a Data object has just the 2 fields (columns in Database terminology) then, as Room requires a primary key, an implicit UNIQUE constraint applies (could be either column or a composite primary key across both). As such adding Data(1,"val") a second time would result in a constraint violation which would result in either
The row being deleted and another inserted (if INSERT OR REPLACE)
This further complicated by the value of autogenerate.
An exception due to the violation.
The insert being skipped if INSERT OR IGNORE were used.
This option could be suitable for a small amount of data but if over used can start to bloat the code and result in it's maintainability being compromised.
If INSERT or IGNORE were utilised (or alternative checks) then this could, at some additional overhead, even be undertaken in the Callback's onOpen method. This being called every time the database is opened.
Pre-packaged Database
If you have lots of initial data, then creating the database externally, including it as an asset (so it is part of the package that is deployed) and then using Room's .createFromAsset (or the rarer used .createFromFile) would be the way to go.
However, the downfall with this, is that Room expects such a database to comply with the schema that it determines and those expectations are very strict. As such just putting together a database without understanding the nuances of Room then it can be a nightmare.
e.g. SQLite's flexibility allows column types to be virtually anything (see How flexible/restricive are SQLite column types?). Room only allows column types of INTEGER, TEXT, REAL or BLOB. Anything else and the result is an exception with the Expected .... Found ... message.
However, the easy way around this is to let Room tell you what the schema it expects is. To do so you create the #Entity annotated classes (the tables), create the #Database annotated class, including the respective entities in the entities parameter and then compile. In Android Studio's Android View java(generated) will then be visible in the explorer. Within that there will be a class that is the same name as the #Database annotated class but suffixed with _Impl. Within this class there is a function/method createAllTables and it includes execSQL statements for all the tables (the room_master_table should be ignored as Room will always create that itself).
The database, once created and saved, should be copied into the assets folder and using .createFromAsset(????) will then result in the pre-packaged data being from the package to the appropriate local storage location.

Android Room Entities: Is it possible to ignore fields by default?

By default, Room creates a column for each field that is defined in the entity. If an entity has fields that I don't want to persist, I have to use the #Ignore annotation.
This poses a problem with inheritance. Annotating all the unwanted fields from a base class becomes unfeasible at a certain point, especially if you have to make your own versions of complex stock objects only to ignore the fields.
Currently, I am using interfaces instead of inheritance to work around that problem, but I would prefer to have a base class for my Room objects.
Do you know a way to ignore fields by default, so I can rather De-Ignore the desired fields instead of the other way around? Preferably in Kotlin?
Edit:
I want to build a treeview of different room entities and it would be nice to have my treeview item as a base class for all of them. But the treeview item implements a lot of stuff, it is not practical to customize all of that just for #Ignore tags. There are workarounds, but i would need less code if i do it this way.
You can use #Ignore on your base classes as well, for instance:
open class MyBaseClass{
#Ignore
open var somethingBasic: Int = 0
}
#Entity(...)
class A : MyBaseClass{
var name: String? = null
}
#Entity(...)
class B : MyBaseClass{
var type: Int = 0
}
But be careful about this because using a base class for different tables is a bit abnormal and it's able to break all your tables somewhere (take migrations as an example). I suggest to take a deeper look on your structure and try to stay away from this :D

Android: use entity class in view

I'm building an android project, I have a database and I create many entity classes (which has all sorts of annotations like #id, #Nullable). Now I need to show the data in my view.
I'm wondering if it's ok to use directly entity classes in the view (e.g. adapter), or it's better to convert them first in VO object? How to organise things in a clearer way ? Do I need to create a converter for each entity ?
Thanks.
Maybe the MVVM pattern is what you are looking for. Your entity classes (Models) are "wrapped" by ViewModels. The ViewModels expose the data to your UI and accept user input.
There's no need for a 1:1 mapping between Models and ViewModels. Your ViewModel (e.g. for a whole screen) can hold several different Models and interact with them.

Retrieving a HashMap LiveData via Room Library

I have a social networking app which displays a list of users, and am looking to have an efficient way of being able to retrieve an object from my LiveData using its primary key.
Example: Retrieve a set of User POJOs from within my LiveData<List<User>> given a LIST of userId Integers (ie, users 12, 5, 7, and 1). I need to be able to look up these users by the userId for display in the appropriate order in the UI.
I believe I want something more like LiveData<Map<Integer, User>>, but how could I implement this using the Room database, without breaking the LiveData callbacks from my local DB -> Room -> LiveData -> UI?
PROPOSAL 1:
Change my Room implementation to somehow return a LiveData containing a HashMap of <userId,User>.
Current Room implementation:
#Query("SELECT * FROM users WHERE user_id in :userIds LIMIT 1")
LiveData<List<User>> getUsers(List<Integer> userIds);
Proposed Room implementation (no idea if something like this is possible or what it would even look like):
#Query("SELECT * FROM users WHERE user_id in :userIds LIMIT 1")
LiveData<**HashMap**<Integer,User>> getUsers(List<Integer> userIds);
PROPOSAL 2:
Have a list of many LiveData objects WITHIN a Map:
Map<Integer,LiveData<User>> liveDataUsers;
This might be something to look into, but I'm worried that having potentially hundreds/thousands of LiveData objects within a map is bad design and could also lead to performance issues / too many open LiveData internal callback threads.
PROPOSAL 3:
Something else??? I feel like I am missing something easy here. How are others looking up objects within their LiveData using only their primaryKey?
edit: this is something I'd like to achieve at the Repo / Model level and not at the activity level, as this LiveData will be re-used throughout the app.

Defining an ActiveAndroid model without creating a table

I have my app's networking code separated into a separate package. This package contains a POJO model that I'm serializing with Gson (using its #Expose annotation):
public class User {
#Expose
String username;
#Expose
String pathToAvatar;
// etc.
}
My app also is using ActiveAndroid as a database wrapper. I would like to extend the .networking.User model and add a couple more database-specific fields (secretKey or something) in my main logic. It would be nice if the networking code did not know its model was being used in a database. Unfortunately, I'm having to have .networking.User extend the ActiveAndroid Model base class (and having to label each networking variable with #Column). This works, with one problem: I'm getting a dummy User database table that corresponds with the networking code's class, even though it is not annotated with #Table, simply because it derives from Model. The main code's table is created correctly.
Can I prevent ActiveAndroid from creating a table when it finds a certain Model? Alternatively, is there a better way to approach this?

Categories

Resources