How manage data classes with empty data in retrofit? - kotlin

I want to send to the API only the data that I indicate in my data class.
data class UserRequestDTO(
val name: String = "",
val surname: String = ""
)
My empty class
val userDTO = UserRequestDTO()
MyService.getService().users()
My request
#POST("/vo/search")
fun users(
#Body userRequestDTO: UserRequestDTO
): Call<ResponseDTO>
But the following json is being sent:
{"name": "", "surname": ""}
How can I have a class in which I only send the data that I fill in? don't want any data to be sent, and if for example I fill in the name, I don't want the last name to be sent

Making them optional should result in not sending them:
data class UserRequestDTO(
val name: String? = null,
val surname: String? = null
)

Related

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

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)

No value passed for parameter 'info' in data class

I have a data class
data class TaxEngineModel(
#Id
val Id: String = "",
val environment: String = "",
val tDescription: String = "",
val EndPoint: String = "",
val Type: String = "",
val info: infoModel,
)
when I want to create an object, I got an error "No value passed for parameter 'info'", how to fix this issue?
val model = TaxEngineModel()
As the error hints: You must pass a value for info or make info nullable. You have default values for all other backfields in your class, but not for info.
Pass a value:
val model = TaxEngineModel(info = someInfoModel)
or make info nullable:
...
val Type: String = "",
val info: infoModel?, // you could optionally assign null as default here
)
or define a default value:
...
val Type: String = "",
val info: infoModel = someInfoModel, // you could maybe create InfoModel here?
)
However in the later case, you'd need a global constant or something else the constructor has access to.

Exclude non-null properties when serializing a Kotlin data class

I often will create a data class in Kotlin that is used internally for data models. Example:
data class MyDataModel(
var id: String? = null,
var ownerId: String,
var name: String,
var isPrivate: Boolean = false,
)
I often need to serialize these classes to JSON. The problem is that some of the class properties are not nullable and there are cases where I need to exclude those fields in the serialization. I haven't found a clean and simple way to do that. The solution I currently use is not to use non-nullable properties and then set those that I don't want serialized to null.
Is there another approach?
Solution using kotlinx.serialization:
Define class, including all fields you want to be serialized, mark it as #Serializable
#Serializable
open class MyDataModelSerializable(
open var id: String? = null
)
Make your data class to be its subtype:
data class MyDataModel(
var ownerId: String,
var name: String,
var isPrivate: Boolean = false,
override var id: String? = null
) : MyDataModelSerializable(id)
Serialize instances of MyDataModel class with serializer for MyDataModelSerializable:
val s = serializer<MyDataModelSerializable>()
println(Json.encodeToString(s, MyDataModel(ownerId = "1", name = "2", id = "3", isPrivate = true))) //{"id":"3"}
println(Json.encodeToString(s, MyDataModel(ownerId = "1", name = "2", isPrivate = true))) //{}
println(Json{encodeDefaults = true}.encodeToString(s, MyDataModel(ownerId = "1", name = "2"))) //{"id":null}

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).

Using Moshi with multiple input fields

I have some JSON that looks like this:
{
"name" : "Credit Card",
"code" : "AUD",
"value" : 1000
}
and am using Moshi to unmarshall this into a data structure like:
data class Account(
#Json(name = "name")
val name: String,
#Json(name = "currency")
val currency: String,
#Json(name = "value")
val value: Int
)
Everything works well. However, I really would like to extract the currency and value parameters into a separate Money object. So my model looks more like:
data class Money(
#Json(name = "currency")
val currency: String,
#Json(name = "value")
val value: Int
)
data class Account(
#Json(name = "name")
val name: String,
#Json(name = "???")
val money: Money
)
The challenge I'm struggling with is how to annotate things so that the Money object can be given two different fields (currency and value) that come from the same level as the parent account.
Do I need to create an intermediate "unmarshalling" object called, say, MoshiAccount and then use a custom adapter to convert that to my real Account object?
I saw How to deseralize an int array into a custom class with Moshi? which looks close (except that in that case, the adapted object (VideoSize) only needs a single field as input... in my case, I need both currency and value)
Any thoughts or suggestions would be much appreciated. Thanks
Moshi's adapters can morph your JSON structure for you.
object ADAPTER {
private class FlatAccount(
val name: String,
val currency: String,
val value: Int
)
#FromJson private fun fromJson(json: FlatAccount): Account {
return Account(json.name, Money(json.currency, json.value))
}
#ToJson private fun toJson(account: Account): FlatAccount {
return FlatAccount(account.name, account.money.currency, account.money.value)
}
}
Don't forget to add the adapter to your Moshi instance.
val moshi = Moshi.Builder().add(Account.ADAPTER).add(KotlinJsonAdapterFactory()).build()
val adapter = moshi.adapter(Account::class.java)