Kotlin's data class, Android Room and custom setter - android

I got an entity for Android Room which looks like that. So far, no worries.
#Entity(tableName = "users",
indices = arrayOf(Index(value = "nickName", unique = true)))
data class User(#ColumnInfo(name = "nickName") var nickName: String,
#ColumnInfo(name = "password") var password: String) {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id: Long = 0
}
Now I need to encrypt the password. With Java, this would simply be done with a setter and that would work.
How would you do that with Kotlin. I cannot find a solution to combine Android Room, custom setters and data classes.

You can try something like this:
#Entity(tableName = "users",
indices = arrayOf(Index(value = "nickName", unique = true)))
data class User(#ColumnInfo(name = "nickName") var nickName: String,
private var _password: String) {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id: Long = 0
#ColumnInfo(name = "password")
var password: String = _password
set(value) {
field = "encrypted"
}
override fun toString(): String {
return "User(id=$id, nickName='$nickName', password='$password')"
}
}
But I wouldn't recommend encrypting password inside Entity or modifying it somehow as it isn't its responsibility and you may face errors with double encryption of your password as when you retrieve your entity from database Room will populate the entity with data which will lead to encryption of already encrypted data.

#Entity(tableName = "users",
indices = arrayOf(Index(value = "nickName", unique = true)))
data class User(#ColumnInfo(name = "nickName") var nickName: String,
#ColumnInfo(name = "password") var password: String) {
var _password = password
set(value): String{
//encrypt password
}
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id: Long = 0
}
This will create a custom setter so every time you set your password you can encrypt it as well inside the setter.

Related

How to have unique Field for the id in Room Android? [duplicate]

This question already has answers here:
add unique constraint in room database to multiple column
(4 answers)
Closed 7 months ago.
I want to achieve unique audio_id for the id.
Here is my entity class
data class Members(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") val id: Int?,
var title: String,
var artist: String,
var album: String,
#ColumnInfo(name = "audio_id") val audioId: Int,
val albumId: String
)
Tried this
#Entity(tableName = "playlist_members", indices = [Index(value = ["id","audio_id"], unique = true)])
not working :)
I've made a test example. So all work.
My entity
#Entity(tableName = "cards", indices = [Index(value = ["data"], unique = true)])
data class CardEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
val data: String
)
My test
#Test
fun testRun() = runBlocking {
db.cardDAO().insert(CardEntity(data = "1"))
var exception = false
try {
db.cardDAO().insert(CardEntity(data = "1"))
} catch (e: SQLiteConstraintException) {
exception=true
}
db.cardDAO().insert(CardEntity(data = "2"))
Assert.assertTrue(exception)
Assert.assertEquals(db.cardDAO().getAll().first().size, 2 )
}
But you want to get unique audio_id for the id. Perhaps it the same like.
data class Members(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "audio_id") val audioId = 0,
var title: String,
var artist: String,
var album: String,
val albumId: String
)

How to build and query a Room DB to return a list of objects of multiple classes?

Bear with me, it's a tricky question and what resources I've found around don't really help me resolve my problem.
I'm trying to build a real estate-oriented app on Kotlin. It must show at some point a RecyclerView with multiple object classes (say: houses, flats, plots, buildings, etc.)
I've seen multiple examples of RVs designed to accept multiple classes, but I'm struggling to put together a DB and the intermediary classes translating between tables and POJOs.
So far I've figured the following:
I must have a Properties table that stores the unique ID for every object, along with another identifier for its type and a series of values common to every property (say, address, price, etc.)
I must have a table for each entity type that can be independently listed as a real estate item (say, a house, a flat, a plot of land, a building, what have you). Each row on those tables will have a primary foreign key referencing its equivalent on the Properties table.
Now for the unexpected habanero. I decided to start sketching out my project on the basis of the RecyclerView Kotlin codelabs Google put together for newbies like me. Therein data is retrieved from the DB in this fashion:
this.plots = Transformations.map(database.RealtorDao.getPlots()) { it.asDomainModel() }
This works smoothly enough when the objects on the list the DB spits at you are all of one single kind, but what happens if you need them to be of different classes so that the adapter can tell them apart?
Or the only way around is just to build a gigantic table with about a hundred columns that will have nulls everywhere, and sort out objects ONLY AFTER they've been parsed in the previously described fashion?
I smashed my head against this wall until I got tired of hearing the squishing sound. I could not get a Room DB to return a list of objects of multiple classes, so I had to adopt a dirtier approach.
If I had worked just with the database classes then probably I could have hacked it, but trying to translate objects of such classes into POJOs to use instead complicated things somewhat.
The workaround I found was to make a master real estate class and accept that it would have lots and lots of null fields on the database. While a far cry from ideal, it works.
Database object classes:
open class DatabaseProperty
{
#ColumnInfo(name = COL_TYPE)
#SerializedName(COL_TYPE)
#Expose
var type: String? = null
#ColumnInfo(name = COL_ADDRESS)
#SerializedName(COL_ADDRESS)
#Expose
var address: String? = null
#ColumnInfo(name = COL_OWNER)
#SerializedName(COL_OWNER)
#Expose
var owner: String? = null
#ColumnInfo(name = COL_PRICE_FINAL)
#SerializedName(COL_PRICE_FINAL)
#Expose
var priceFinal: Long? = null
#ColumnInfo(name = COL_PRICE_QUOTED)
#SerializedName(COL_PRICE_QUOTED)
#Expose
var priceQuoted: Long? = null
/**
* No args constructor for use in serialization
*/
constructor()
#Ignore
constructor
(
type: String,
address: String,
owner: String,
priceFinal: Long,
priceQuoted: Long
) : super() {
this.type = type
this.address = address
this.owner = owner
this.priceFinal = priceFinal
this.priceQuoted = priceQuoted
}
}
#Entity
(
tableName = TABLE_RE,
indices =
[
Index(value = [COL_RE_ID], unique = true)
],
foreignKeys =
[
ForeignKey
(
entity = DatabaseRealEstate::class,
parentColumns = arrayOf(COL_RE_ID),
childColumns = arrayOf(COL_PARENT_ID),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class DatabaseRealEstate
(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = COL_RE_ID)
var id: Int? = null,
#ColumnInfo(name = COL_PARENT_ID)
var parent_id: Int? = null,
#Embedded(prefix = RE)
var property: DatabaseProperty? = null,
#ColumnInfo(name = COL_PARCEL_FRONT) // Plot front
#SerializedName(COL_PARCEL_FRONT)
#Expose
var front: Float? = null,
#ColumnInfo(name = COL_PARCEL_SIDE) // Plot side
#SerializedName(COL_PARCEL_SIDE)
#Expose
var side: Float? = null,
#ColumnInfo(name = COL_AREA) // Plot area
#SerializedName(COL_AREA)
#Expose
var area: Float? = null,
#ColumnInfo(name = COL_CATASTER)
#SerializedName(COL_CATASTER)
#Expose
var cataster: String? = null,
#ColumnInfo(name = COL_ZONIFICATION)
#SerializedName(COL_ZONIFICATION)
#Expose
var zonification: String? = null,
)
data class RealEstateWithSubunits
(
#Embedded
val re: DatabaseRealEstate? = null,
#Relation
(
parentColumn = COL_RE_ID,
entityColumn = COL_PARENT_ID,
entity = DatabaseRealEstate::class
)
var subunits: List<DatabaseRealEstate>? = null,
#Relation
(
parentColumn = COL_RE_ID,
entityColumn = COL_PARENT_ID,
entity = DatabaseChamber::class
)
var chambers: List<DatabaseChamber>? = null
)
fun List<RealEstateWithSubunits>.asRESUBDomainModel() : List<RealEstate>
{
return map { obj ->
RealEstate(
id = obj.re!!.id!!,
type = obj.re.property!!.type!!,
address = obj.re.property!!.address!!,
owner = obj.re.property!!.owner!!,
priceFinal = obj.re.property!!.priceFinal!!,
priceQuoted = obj.re.property!!.priceQuoted!!,
parent_id = obj.re.parent_id,
front = obj.re.front,
side = obj.re.side,
area = obj.re.area,
cataster = obj.re.cataster,
zonification = obj.re.zonification,
chambers = obj.chambers!!.asChamberDomainModel(),
subunits = obj.subunits!!.asREDomainModel()
)
}
}
fun List<DatabaseChamber>.asChamberDomainModel(): List<Chamber>
{
return map {
Chamber(
id = it.id,
parent_id = it.parent_id,
front = it.front,
side = it.side,
area = it.area
)
}
}
fun List<DatabaseRealEstate>.asREDomainModel(): List<RealEstate>
{
return map { obj ->
RealEstate(
id = obj.id!!,
type = obj.property!!.type!!,
address = obj.property!!.address!!,
owner = obj.property!!.owner!!,
priceFinal = obj.property!!.priceFinal!!,
priceQuoted = obj.property!!.priceQuoted!!,
parent_id = obj.parent_id,
front = obj.front,
side = obj.side,
area = obj.area,
cataster = obj.cataster,
zonification = obj.zonification,
chambers = ArrayList(),
subunits = ArrayList()
)
}
}
Model object classes:
interface BaseProperty {
var id: Int
var type: String
var address: String
var owner: String
var priceFinal: Long
var priceQuoted: Long
}
data class RealEstate(
override var id: Int = -1,
override var type: String = "",
override var address: String = "",
override var owner: String = "",
override var priceFinal: Long = 0,
override var priceQuoted: Long = 0,
var parent_id: Int?,
var front: Float?,
var side: Float?,
var area: Float?,
var cataster: String?,
var zonification: String?,
var subunits: List<RealEstate>? = null,
var chambers: List<Chamber>? = null
) : BaseProperty
{
fun hasParent() : Boolean
{
if (parent_id == null)
{
return false
}
return true
}
}
I haven't yet found a better approach, so if someone does, I'm welcoming it with open arms.

How to autogenerate a Room database id without providing an id

I am trying to create a room database and I want each item inserted into it to have its own unique id without me having to provide it, The problem is when I try to insert new items into the database I get an error asking me to provide an id.
Here is my entity:
#Entity(tableName = "notes_table")
data class Note(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
Is there a way to have the database create its own auto-generated auto-increasing id column without having me having to add it like this:
val item = Note(id, item, priority)
insert(item)
And instead do this:
val item = Note(item, priority)
insert(item)
Create a constructor that takes item and priority as arguments
#Entity(tableName = "notes_table")
data class Note (var item: String,
#ColumnInfo(name = "priority")
var priority: String) {
#PrimaryKey(autoGenerate = true)
var id: Long = 0,
//.....
}
You can just simply give the id a default value and put that at the end:
#Entity(tableName = "notes_table")
data class Note(
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
#PrimaryKey(autoGenerate = true) //must be at the end
val id: Int = 0 //Long type recommend
)
Then you can:
val item = Note(item, priority)
insert(item)
Because your data class Note has three parameter.
So you you have to create Note by passing three parameter.
It is nothing to do with autogenerate or room.

Why do I have to make a setter for an auto-incremented column in a database?

In my android app I have created a database using the room persistance library. It contains of a table called stack with the columns stack_id, col_1, col_2 and stack_name. Here's the code for the entity class:
#Entity
class Stack(
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String
) {
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0
}
Question 1: Is the stack_id correctly implemented? I found this solution to set en auto-incremented column to 0 and it will automaticly auto-increment the value, but it doesn't make sense to me. So, is this correct?
Question 2: Whe I want to build the app, it throws the error:
Cannot find setter for field. private final int stackId = 0;
But it would be nonsense to make a setter for an auto-incremented value. So, should I make a setter or is there another solution?
You are using concrete class so you need to provide get() and set() for properties.
Use data class which are build for this purpose only .
#Entity
data class Stack(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val id: Int? = null,
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String)
You need to move
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0
to the constructor.
#Entity
class Stack(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0,
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String
)
Why this works? The property is val, so is final/immutable, but the assignment in the constructor is a default value which can be overridden by passing in a value.
However in your example the property is in the class body and is assigned the value 0 on init, and cannot be changed as property is immutable.
The second option, as pointed out, is making the property mutable by using var instead of val.

Check if an entry is already in a livedata list wihout looping through the list

I am trying to build an app to help me track some of the tasks we have to do in the game.
I have a Firebase Firestore database that store all the tasks and I download at the application launch the data and add only the one I don't have.
Here is my entry model:
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long?,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String,
#ColumnInfo(name = "target") val target: Int = 0,
#ColumnInfo(name = "position") val position: Int = 0,
#ColumnInfo(name = "starred") val starred: Boolean = false
) {
constructor(): this(null, "", "", 0, 0, starred = false)
}
Since I download the document from the firestore database I cannot set an ID before inserting the entries in my SQLite database.
This means that I cannot use the "contains" method on my livedata list (since the entries I recieve has a "null" id and the one from the database has an id). I need to loop though all the data, here is the code:
#WorkerThread
suspend fun insertEntry(entry: Entry) {
for (doc in entriesList.value!!){
if (doc.description == entry.description && doc.title == entry.title) {
Log.d("MAIN_AC", "Entry already saved $entry")
return
}
}
entryDAO.insertEntry(entry)
}
My code works but I am not satisfied with it, is there a better way to make this happen? I was hoping that the contains method could ignore some arguments (in my case the autogenerated ID)
One way you can go about, assuming you are using Room, it is to annotate your insert function (in the relevant DAO) with OnConflictStrategy.IGNORE.
e.g.
#Dao
interface EntryDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(list: List<Entry>)
// or (if you want the inserted IDs)
// fun insert(list: List<Entry>) : LongArray
}
Be sure to also annotate your entity with the relevant unique index.
e.g.
#Entity(tableName = "entry_table",
indices = [Index(value = ["title", "description"], unique = true)]
)
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String
//...
)
Primary keys should not be null-able, you can .map to Entry wit uid = 0. If you are using the same entity model both locally and remotely that is probably not the best idea.

Categories

Resources