Room Database - Compare values in the database to userinput for validation - kotlin

I am creating an app where the user can log in or register for an account. I have created the register screen and it's successfully saving data into the database. However, I am now trying to integrate some validation. For example, the usernames would have to be unique and the email can't already exist.
I tried to write a custom query of-course to print out all the rows in the username column like this:
SELECT userName from cx_table
and I also tried to write a separate custom query of-course to print out all the rows in the email column like this:
SELECT email from cx_table
Then my approach was to take the user input and compare it to the values returned by that column, if it exists, print an error message. But when I run the app, I get the following error message
The columns returned by the query does not have the fields [id,firstName,lastName,password,address,city,postalcode,email,phone] in com.cxpro.data.Customer even though they are annotated as non-null or primitive. Columns returned by the query: [userName]
here is all my code for the Room Database:
Customer.kt
#Entity(tableName = "cx_table")
data class Customer(
#PrimaryKey(autoGenerate = true)
val id: Int,
val firstName: String,
val lastName: String,
val userName: String,
val password: String,
val address: String,
val city: String,
val postalcode: String,
val email: String,
val phone: String
)
CustomerDao.kt
#Dao
interface CustomerDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun addCustomers(customer: Customer)
#Query("SELECT * FROM cx_table ORDER BY id ASC")
fun readAllData(): LiveData<List<Customer>>
#Query("SELECT userName FROM cx_table")
fun readUserName(): LiveData<List<Customer>>
}
CustomerDatabase.kt
#Database(entities = [Customer::class],version = 1, exportSchema = false)
abstract class CustomerDatabase: RoomDatabase() {
abstract fun customerDao(): CustomerDao
companion object{
#Volatile
private var INSTANCE: CustomerDatabase? = null
fun getDatabase(context: Context): CustomerDatabase{
val tempInstance = INSTANCE
if(tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
CustomerDatabase::class.java,
"customer_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
CustomerRepository.kt
class CustomerRepository(private val customerDao: CustomerDao) {
val readAllData: LiveData<List<Customer>> = customerDao.readAllData()
val readUserName: LiveData<List<Customer>> = customerDao.readUserName()
suspend fun addCustomer(customer: Customer){
customerDao.addCustomers(customer)
}
}
CustomerViewModel.kt
class CustomerViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Customer>>
val readUserName: LiveData<List<Customer>>
private val repository: CustomerRepository
init {
val customerDao = CustomerDatabase.getDatabase(application).customerDao()
repository = CustomerRepository(customerDao)
readAllData = repository.readAllData
readUserName = repository.readUserName
}
fun addCustomer(customer: Customer){
viewModelScope.launch(Dispatchers.IO){
repository.addCustomer(customer)
}
}
}
How can I validate that the username and/or email doesn't already exist in the table?

Then my approach was to take the user input and compare it to the values returned by that column, if it exists, print an error message. But when I run the app, I get the following error message
This is because there are insufficient values to build a Customer object. As you just returning a single value per row you can use List<String> instead of List<Customer>. If multiple values then you need an object probably a POJO. and that the object field/variable names match the columns names
However, rather then having to loop through 2 lists you may wish to consider changing the Customer Entity to :-
#Entity(tableName = "cx_table",
indices = [
Index(value = ["userName"],unique = true),
Index(value = ["email"],unique = true)]
)
data class Customer(
#PrimaryKey(autoGenerate = true)
val id: Int,
val firstName: String,
val lastName: String,
val userName: String,
val password: String,
val address: String,
val city: String,
val postalcode: String,
val email: String,
val phone: String
)
and also changing the insert Dao to be :-
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun addCustomers(customer: Customer): Long //<<<<< ADDED Long
You can the check the return value if it is greater than 0 then the row was inserted, otherwise the row wasn't inserted and thus was invalid.
That is, as the indexes on username and email are UNIQUE then attempting to insert whilst duplicating either would result in a conflict, which is ignored. However, the row is not inserted and hence -1 being returned.
Yet another option could be to test the values e.g.
#Query("SELECT count(*) FROM customer WHERE userName=:userNameToCheck OR email=emailToCheck")
fun validateNewCustomer(userNameToCheck: String,emailToCheck): Int
If the result is 0 then OK to insert. You could split into two checks if you wanted to check them individually.
You could ascertain whether it is userName or email that is resulting in the invalid (non-zero) result using something like:-
#Query("SELECT ((SELECT count(*) FROM customer WHERE username=:userNameToCheck) + (SELECT count(*) * 1000 FROM customer WHERE email=:emailToCheck));")
fun validateNewCustomer(userNameToCheck: String,emailToCheck): Int
If the returned value is 0 then valid, if less than 1000 (if greater than 1 then duplicates exist) then the userName is invalid, if greater 1000 then the email is invalid, if greater than 1000 but not an exact multiple of 1000 then both are invalid.
1000 caters for up to 998 userName duplicates (if there is a UNIQUE index on userName then there should only be 1, similar for email)

Related

How to manipulate a specific set of data from Live or Flow Data Kotlin MVVM?

I have three Room entities. An entity Person includes id and name, entity Holiday includes id, personId, onHoliday and Sickness entity includes id, personId and onSickness.
Also, I have a POJO Entity called ScreenPOJO that includes viewTypeId and personName.
My goal is to get the person's name (from Person) and viewTypeId that I can observe in Activity.
The viewTypeId is an Integer that depends on the onHoliday - true/false and onSickness true/false. So let's say when the onHoliday is false and onSickness is false the viewTypeId = 1, when onHoliday is true then viewTypeId = 2 and when onSickness is true then viewTypeId = 2.
In this example, it is achievable by creating a query and returning the result by using the POJO Entity.
Sometimes the query can be too complex and for that reason, I would like to somehow merge all three Live/Flow data together using the person_id. I read that I can use MediatorLiveData, however, I do not have experience yet to put all the data together and return only the result that I need (person name and viewTypeId).
Person Entity:
#Entity(tableName = "person_table")
data class Person(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id") val id: Int,
#SerializedName("name")
#ColumnInfo(name = "name") val name: String,
)
Sickness Entity:
#Entity(tableName = "sickness_table")
data class Sickness(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id") val id: Int,
#SerializedName("person_id")
#ColumnInfo(name = "person_id") val personId: Int,
#SerializedName("on_sickness")
#ColumnInfo(name = "on_sickness") val onSickness: Boolean
)
Holiday Entity:
#Entity(tableName = "holiday_table")
data class Holiday(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id") val id: Int,
#SerializedName("person_id")
#ColumnInfo(name = "person_id") val personId: Int,
#SerializedName("on_holiday")
#ColumnInfo(name = "on_holiday") val onHoliday: Boolean
)
ScreenPOJO Entity:
data class ScreenPOJO(
val viewTypeId: Int,
val personName: String
)
ViewModel, the data are originally as Flow :
val allPersons: LiveData<List<Person>> = repository.allPersons.asLiveData()
val allHolidays: LiveData<List<Holiday>> = repository.allHolidays.asLiveData()
val allSickness: LiveData<List<Sickness>> = repository.allSickness.asLiveData()
ViewModel, example of insert Person:
private val _insertPersonStatus = MutableLiveData<ViewModelStatus>()
val insertPersonStatus: LiveData<ViewModelStatus> = _insertPersonStatus
fun insertPerson(person: Person) = viewModelScope.launch {
try {
val insertedRowId = repository.insertPerson(person)
if (insertedRowId > -1) {
_insertPersonStatus.postValue(ViewModelStatus.SUCCESS(insertedRowId,ViewModelStatus.SUCCESS.Type.VM_INSERT))
} else {
_insertPersonStatus.value = ViewModelStatus.FAIL("Fail",ViewModelStatus.FAIL.Type.VM_INSERT)
}
} catch (ex: Exception) {
_insertPersonStatus.value = ViewModelStatus.EXCEPTION(ex.localizedMessage.toString(),ViewModelStatus.EXCEPTION.Type.VM_INSERT)
}
}
I think it is better to modify the data layer in a way, which will allow you to write less code in the business-logic and view layers, therefore the code will be simpler and cleaner which is always a good sign.
Therefore, instead of merging LiveData you should think about redesigning Person data class.
Since I don't see much sense in storing holidays and sickness separately from persons, I assume you should somehow store holiday and sickness related data in the person object.
I suggest something like this (I will omit room annotations for simplicity):
data class Person(
val id: Long,
val name: String,
val onHoliday: Boolean,
val onSickness: Boolean
)
In case when the person has multiple holidays or sickness days, you can do something like this (which is kinda common practice nowadays):
data class Person(
val id: Long,
val name: String,
val holidays: List<Holiday>,
val sicknessDays: List<Sickness>
)
I am not sure that it is easy to insert a collection in Room, but there are workarounds here. Maybe you will also need #Embedded annotation to keep the POJO's inside of the POJO

Select where id not in list for IntIdTable in Kotlin Exposed

In Kotlin exposed, I have a IntIdTable like this:
object UserTable : IntIdTable("user", "id") {
override val id: Column<EntityID<Int>>
get() = super.id
val name = varchar("name", 128)
val activeFlg = integer("active_flg")
...
}
So, I call the transaction that get all record from test table with exclude ids like this:
fun getAllWithExcludeIds(excludeIds: List<Int>): List<User> {
return transaction {
UserTable
.select { UserTable.id notInList excludeIds }
}
}
Then I got a type miss match error:
Type mismatch.
Required:
EntityID<Int>
Found:
Int
How can I select list of users which has id not in specified excludeIds list? Thank you!

How to find the updated fields between a payload and an entity fetched from DB and create an object having fields with updated values and rest Null

Given an update request for a record in DB, I have to find a difference between the payload and existing data in DB then create a new Object which has updated fields with Payload values and rest as Null.
I have created a function which gives me a list of field names which were updated, But I'm unable to create a new object which has values for only these updated fields.The problem is that the function uses "field: Field in cpayload.javaClass.declaredFields" which is kind of generic so I'm unable to set these fields.
fun findupdatedFieldsList(cpayload: Customer, cEntity: Customer): List<String> {
// var customerToPublish = Customer()
val updatedFieldsList: MutableList<String>
updatedFieldsList = ArrayList()
for (field: Field in cpayload.javaClass.declaredFields) {
field.isAccessible = true
val value1 = field.get(cpayload).toString()
val value2 = field.get(cEntity).toString()
!Objects.equals(value1, value2).apply {
if (this) {
// customerToPublish.birthDate=field.get(cpayload).toString()
updatedFieldsList.add(field.name)
}
}
}
return updatedFieldsList
}
#Entity
#Table
data class Customer(
#Id
val partyKey: UUID,
var preferredName: String?,
var givenName: String?,
var lastName: String?,
var middleName: String?,
var emailAddress: String,
var mobileNumber: String,
val birthDate: String?,
val loginOnRegister: Boolean,
var gender: Gender?,
var placeOfBirth: String?,
var createdDate: LocalDateTime = LocalDateTime.now(),
var updatedDate: LocalDateTime = LocalDateTime.now()
)
Desired Output
val customer = Customer(
preferredName = Updated name,
partyKey = partyKey.value,
givenName = Updated name,
lastName = null,
middleName = null,
emailAddress = Updated email,
mobileNumber = null,
birthDate = null,
gender = null,
placeOfBirth = null
)
I was able to construct a solution using Kotlin's reflect. It is generic and can be applied to any Kotlin class that have primary constructor. Unfortunately it won't work with Java classes
You would need to add kotlin-reflect package to your build tool config, e.g. for Gradle:
implementation 'org.jetbrains.kotlin:kotlin-reflect:XXXXXX'
First we will build a function to extract updated properties. Please take a note that we also need to extract properties that are mandatory (non-nullable and without default). We add them to a map of propertyName -> propertyValue:
fun Map<String?, KParameter>.isOptional(name: String) = this[name]?.isOptional ?: false
fun <T : Any> findUpdatedProperties(payload: T, entity: T): Map<String, Any?> {
val ctorParams = payload::class.primaryConstructor!!.parameters.associateBy { it.name }
return payload::class.memberProperties.map { property ->
val payloadValue = property.call(payload)
val entityValue = property.call(entity)
if (!Objects.equals(payloadValue, entityValue) || (!ctorParams.isOptional(property.name))) {
property.name to payloadValue
} else {
null
}
}
.filterNotNull()
.toMap()
}
Then we call this function and construct a new instance of provided class:
fun <T : Any> constructCustomerDiff(clazz: KClass<T>, payload: T, entity: T): T {
val ctor = clazz.primaryConstructor!!
val params = ctor.parameters
val updatedProperties = findUpdatedProperties(payload, entity)
val values = params.map { it to updatedProperties[it.name] }.toMap()
return ctor.callBy(values)
}
Take a note that missing primary constructor will throw NullPointerException because of use of !!.
We could call this funcion as constructCustomerDiff(Customer::class, payload, entity), but we can do better with reified types:
inline fun <reified T : Any> constructCustomerDiff(payload: T, entity: T): T {
return constructCustomerDiff(T::class, payload, entity)
}
Now we can use this function in convenient Kotlin style:
val id = UUID.randomUUID()
val payload = Customer(
partyKey = id,
preferredName = "newName",
givenName = "givenName"
)
val entity = Customer(
partyKey = id,
preferredName = "oldName",
givenName = "givenName" // this is the same as in payload
)
val x = constructCustomerDiff(payload, entity)
assert(x.partyKey == id && x.givenName == null || x.preferredName == "newName")

Match string to object property

I have a set of policies that I want to match a request. If the policy exists, I want to match the request and see if the value matches.
The policies are List<Policies> -> (key: String, value: String) and the request can contain different keys.
Example:
The policies are a set of rules that the request should match.
class Policy {
val key: String,
val value: String
}
The request is a data class that contains different values (all optional), for example surname, firstName, address, ++++
data class Request (
id: Long = 12,
firstName: String = "test",
surname: String = "test",
address: String = "somewhere"
...// more fields
)
The list of policies can look like this (List):
List<Policy> => [
{
key: "surname",
value: "test"
},
{
key: "firstName",
value: "test"
}
]
I don't know how I can match the policies with the request. The Policy.key is a String and the Request can contain all different variations of properties.
How do I match the List of policies with my data class Request?
For your puroposes you need use reflection (you want to find field by name and get value), or change something in your model.
Solution with reflection can be like following:
data class Policy(
val key: String,
val value: String?
)
data class Request(
val id: Int,
val firstName: String? = null,
val surname: String? = null,
val address: String? = null
)
class PolicyException : Exception()
fun checkPolicies(request: Request, policies: List<Policy>) {
policies.forEach { policy ->
val member = request::class.members.find { member -> member.name == policy.key }
val requestMemberValue = member?.call(request)
if (requestMemberValue != policy.value) throw PolicyException()
}
}
fun main(args: Array<String>) {
println("Hello, reflection!")
checkPolicies(Request(id = 0, firstName = "Johnn"), listOf(Policy("firstName", "John")))
}
Also, I changed your policy model to handle nullable values (and still handling properly "null" as string).
But, with this solution you have to be very careful with changing model names. And remeber to do not obfuscate your models.
Also, quite better soltuion is adding Annotation which keeps policy name as annotation property (then problem with changing field name in app will disappear).

Kotlin generate constructor that sets default values to nulled arguments

Let's take the class of a data class:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
)
When calling this function from Kotlin, it is pretty straightforward. I can simply use the named-argument syntax to do so. Calling from Java, I have to specify all values, or use the #JvmOverloads annotation, which generates the following constructors (in addition to the constructor that kotlin generates with the bit-mask for default values):
User(int userNumber, #NotNull String name, #NotNull List userGroups,
#NotNull String screenName)
User(int userNumber, #NotNull String name, #NotNull List userGroups)
User(int userNumber, #NotNull String name)
User(#NotNull String name)
Now, if I want to create a User object in Java equivalent to User(name="John Doe", userGroups=listOf("admin", "super") I can't do it with the above constructors. I CAN however do it if I put val userNumber: Int = -1 at the end in the data class declaration (the generation of constructors seems to depend on the order the optional arguments are defined in). Which is fine, because expecting kotlin to generate all permutations is going to heavily bloat some classes.
The biggest problem that tools like Jackson simply don't work as they have no idea which constructor to use (and not like I can annotate one of the generated ones specially).
So, is there a way to generate a (single) constructor like:
User(Integer userNumber, String name, List<String> userGroups, String screenName) {
this.userNumber = (userNumber == null) ? -1 : userNumber;
this.userGroups = (userGroups == null) ? Collections.emptyList() : userGroups;
//...
}
Currently I am using the above approach, but manually defining the constructors where I need them.
EDIT
I should clarify, creating a similar constructor doesn't work, obviously because both the signatures would clash on the JVM. This is what it would like in my case:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
) {
companion object {
#JvmStatic
#JsonCreator
fun constructionSupport(
#JsonProperty("userNumber") userNumber : Int?,
#JsonProperty("name") name : String,
#JsonProperty("userGroups") userGroups : List<String>?,
#JsonProperty("screenName") screenName : String?
) = User(
userNumber = userNumber ?: -1,
name = name,
userGroups = userGroups ?: emptyList(),
screenName = screenName ?: "new-user"
)
}
}
Also note the redundancy where I have to write the default values for the properties twice. I Now that I look at it, I doubt there exists a solution for this. Maybe this is a good use-case for a kapt based side-project of mine :)
Better solution is to add possibility to library understand Kotlin functional. For example, for Jackson exists jackson-module-kotlin. With this library we can use default arguments in data classes.
Example:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups: List<String> = emptyList(),
val screenName: String = "new-user"
)
fun main(args: Array<String>) {
val objectMapper = ObjectMapper()
.registerModule(KotlinModule())
val testUser = User(userNumber = 5, name = "someName")
val stringUser = objectMapper.writeValueAsString(testUser)
println(stringUser)
val parsedUser = objectMapper.readValue<User>(stringUser)
println(parsedUser)
assert(testUser == parsedUser) {
println("something goes wrong")
}
}
After kicking this around for a minute, I think I found a solution that may work well here. Simply define a top level function in the same source file, that will build the object. Perhaps like so:
fun build_user(userNumber: Int?, name: String, userGroups: List<String>?, screenName: String?) : User {
return User(if(userNumber !== null) userNumber else -1, name, if(userGroups !== null) userGroups else emptyList(),
if(screenName !== null) screenName else "new-user")
}
Then when you need it, you simply call it from Java:
User user = UserKt.build_user(null, "Hello", null, "Porterhouse Steak");
System.out.println(user);
Output from the example:
User(userNumber=-1, name=Hello, userGroups=[], screenName=Porterhouse Steak)
The method is somewhere between a constructor and a builder. It beats hammering out a full-blown Builder object, and avoids cluttering your data class with unnecessary Java-interop glue code messiness.
See Package Level Functions for more information.