How to define am nullability constrain for multiple fields in Kotlin - kotlin

I have a class Apartment. I need to make all properties nullable per field but not the following three (shareNumber, shareNrFrom, debentureNumber) at the same time. The constraint that I added in the constructor is supposed to do the job, but instead, it fails every time since all the values are null when the check is performed. When I try to debug it and place a breakpoint in the constructor it appears to be that all fields are null, even tho I do provide a couple of them when I instantiate the class.
package info
import arrow.optics.optics
import io.swagger.annotations.ApiModel
import io.swagger.annotations.ApiModelProperty
#optics
#ApiModel(description = "en: Apartment")
data class Apartment(
#get:ApiModelProperty("en: Name of the Cooperative", required = false)
val housingCooperative: String? = null,
#get:ApiModelProperty("en: OrgNr of the Cooperative", required = false)
val companyOrgNo: String? = null,
#get:ApiModelProperty("en: shareNumber", required = false)
val shareNumber: String? = null,
#get:ApiModelProperty("en: shareNrFrom", required = false)
val shareNrFrom: String? = null,
#get:ApiModelProperty("en: debentureNumber", required = false)
val debentureNumber: String? = null,
) {
init {
require(shareNumber!=null || shareNrFrom!=null || debentureNumber!=null) { "shareNumber, shareNrFrom and debentureNumber cannot be nullable at the same time" }
}
class Builder internal constructor(
private var housingCooperative: String? = null,
private var companyOrgNo: String? = null,
private var shareNumber: String? = null,
private var shareNrFrom: String? = null,
private var debentureNumber: String? = null
) {
fun housingCooperative(housingCooperative: String): Builder = also {this.housingCooperative = housingCooperative}
fun companyOrgNo(companyOrgNo: String): Builder = also {this.companyOrgNo = companyOrgNo}
fun shareNumber(shareNumber: String): Builder = also {this.shareNumber = shareNumber}
fun shareNrFrom(shareNrFrom: String): Builder = also {this.shareNrFrom = shareNrFrom}
fun debentureNumber(debentureNumber: String): Builder = also {this.debentureNumber = debentureNumber}
fun build(): Apartment = Apartment(
housingCooperative = housingCooperative,
companyOrgNo = companyOrgNo,
shareNumber = shareNumber,
shareNrFrom = shareNrFrom,
debentureNumber = debentureNumber
)
}
companion object {
#JvmStatic
fun builder(): Builder = Builder()
}
}
This is how I instantiate the class.
apartment = Apartment(
shareNrFrom = "12",
debentureNumber = "34"
)

Related

How to test a service function in kotlin which uses a late init Id?

I am new to testing in kotlin and I was wondering how I can test this function:
this is my SegmentService file:
fun createSegmentFromUserIds(userIds: List<String>, name:String, appId: String): Segmentation {
val filter = createUserIdFilter(userIds)
val createSegmentRequest = CreateSegmentRequest(
name = name, attribute = filter, type = SegmentType.STATIC
)
val segmentation = segmentMetaService.saveSegmentInfo(createSegmentRequest, appId)
querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id)
return segmentation
}
this is the saveSegmentInfo function:
fun saveSegmentInfo(createSegmentFilter: CreateSegmentRequest, appId: String): Segmentation {
val segmentInfo = segmentationRepository.save(createSegmentFilter.let {
Segmentation(
attribute = it.attribute, name = it.name, appId = appId, type = it.type
)
})
logger.info("Segment info saved with id: ${segmentInfo.id}, name: ${segmentInfo.name}")
return segmentInfo
}
and this is the Segmentation Document
#Document(Segmentation.COLLECTION_NAME)
class Segmentation(
#Field(ATTRIBUTE)
val attribute: Filter,
#Field(NAME)
val name: String,
#Indexed
#Field(APP_ID)
val appId: String,
#Field(STATUS)
var status: SegmentStatus = SegmentStatus.CREATED,
#Field(TYPE)
var type: SegmentType = SegmentType.STATIC,
#Field(USER_COUNT)
var userCount: Long? = null,
) {
#Id
lateinit var id: String
#Field(CREATION_DATE)
var creationDate: Date = Date()
}
I have written this test for it:
class SegmentServiceTest {
private val segmentMetaService = mock(SegmentMetaService::class.java)
private val querySegmentService = mock(QuerySegmentService::class.java)
private val converterService = mock(ConverterService::class.java)
private val userAttributeService = mock(UserAttributeService::class.java)
private val segmentConsumerUserInfoProducer = mock(SegmentConsumerUsersInfoProducer::class.java)
private val segmentationRepository = mock(SegmentationRepository::class.java)
#Test
fun `createSegmentFromUserIds should create a new segment`() {
val segmentService = SegmentService(
segmentMetaService = segmentMetaService,
querySegmentService = querySegmentService,
converterService = converterService,
userAttributeService = userAttributeService,
segmentConsumerUserInfoProducer = segmentConsumerUserInfoProducer
)
val userIds = listOf("user-1", "user-2", "user-3")
val filter = AndFilter(
operations = listOf(
AndFilter(
operations = listOf(
StringListContainsFilter(
field = "userId", operationType = StringQueryOperationType.IN, values = userIds
)
)
)
)
)
val createSegmentRequest = CreateSegmentRequest(
name = "segment-name", attribute = filter, type = SegmentType.STATIC
)
val segment = Segmentation(attribute = filter, name = "segment-name", type = SegmentType.STATIC, appId = "app-id" )
`when`(segmentationRepository.save(any())).thenReturn(segment)
`when`(segmentMetaService.saveSegmentInfo(createSegmentRequest, "app-id")).thenReturn(segment)
val createdSegment = segmentService.createSegmentFromUserIds(userIds = userIds, name = "segment-name", appId = "app-id")
assertEquals(segment, createdSegment)
}
}
but I am facing this error:
kotlin.UninitializedPropertyAccessException: lateinit property id has not been initialized
So what am I doing wrong here?
How can I initialize the Id? or should I refactor my code so for it to become testable?
I think you are mocking / providing the answers to the wrong calls.
you mock segmentationRepository.save(..) but this call won't ever be made since that call is inside segmentMetaService.saveSegmentInfo(...) which is mocked so the real code is not called.
when you call segmentService.createSegmentFromUserIds(..), my guess (stack trace would be useful on the error, BTW), is that this method proceeds passed the invocation of segmentMetaService.saveSegmentInfo(...) but in the next line you call querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id) which is accessing the uninitialised id.
The fix will be to set the segment.id when you set up the object for the answer to segmentMetaService.saveSegmentInfo(..)

How to assign a string to another string of a data class member?

I'm trying to assign a string value to an string attribute part of a data class object and it's not working:
appointmentInfo.mySymptoms?.medsDescription = "string description"
where,
data class AppointmentInfo(var id : Int? = null,
var planType : String? = null,
var dateFormat : DateFormat,
var mySymptoms : MySymptoms? = null) : Parcelable
#Parcelize
data class MySymptoms(var grantAccess : Boolean,
var takingMeds : Boolean,
var medsDescription : String? = null,
var symptomList : List<String>? = null,
var symptomsDescription : String? = null,
var generalDescription : String? = null ```
**appointmentInfo.mySymptoms?.medsDescription is always null**
Make sure the variables are assigned properly.
Code
data class AppointmentInfo(
var mySymptoms : MySymptoms? = null,
)
data class MySymptoms(
var medsDescription : String? = null,
)
fun main() {
var appointmentInfo = AppointmentInfo()
appointmentInfo.mySymptoms = MySymptoms()
appointmentInfo.mySymptoms?.medsDescription = "string description"
println(appointmentInfo.mySymptoms?.medsDescription)
}
Output
string description
Kotlin Playground link
You should use copy() function of data class https://kotlinlang.org/docs/data-classes.html#copying as Jet Brains suggests all data class member should be read only val

FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY) when add data to table room

I have to tables CheckListModel and CheckListPoints, it is one to n relationship, when i try to add
data in DB CheckListModel adds correctly, but when code gose to add CheckListPoints i got this error. I have no idea why this happening
This is my DB
#Database(entities = [CheckListModel::class,CheckListPoints::class],version = 4,exportSchema = false)
abstract class CheckListDB : RoomDatabase() {
abstract fun checkListDBDao():CheckListModelDBDao
companion object {
#Volatile
private var instance: CheckListDB? = null
fun getInstance(context: Context):CheckListDB{
return instance ?: synchronized(this){
instance?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): CheckListDB{
return Room.databaseBuilder(context,CheckListDB::class.java,"check_list_model").fallbackToDestructiveMigration().build()
}
}
}
entityes
#Entity(
tableName = "check_list_point",
foreignKeys = [
ForeignKey(entity = CheckListModel::class, parentColumns = ["checkListModelID"],childColumns = ["checkListColumnID"],onDelete = ForeignKey.CASCADE)
],
indices = [Index("checkListColumnID")]
)
data class CheckListPoints(
#ColumnInfo(name = "correctly")
var correctly: Boolean,
#ColumnInfo(name = "requirement")
var requirement: String,
#ColumnInfo(name = "passed")
var passed: Boolean,
#ColumnInfo(name="checkListColumnID")
val checkListColumnID: Long,
#PrimaryKey(autoGenerate = true)
val checkListPointsModelID: Long = 0L
): Serializable
#Entity(tableName = "check_list_model")
data class CheckListModel (
#ColumnInfo(name = "check_list_name")
val checkListName: String,
#ColumnInfo(name = "check_list_count")
val checkListCount: Int,
#ColumnInfo(name = "check_list_result")
val checkListResult: Int,
#ColumnInfo(name = "description")
val description: String,
#PrimaryKey(autoGenerate = true)
val checkListModelID: Long = 0L
) : Serializable
relationship
data class CheckListWithCheckListModel(
#Embedded val CheckList: CheckListModel,
#Relation(
parentColumn = "checkListModelID",
entityColumn = "checkListColumnID"
)
val checkListPoints: List<CheckListPoints>
)
this is dao
#Dao
interface CheckListModelDBDao {
#Insert
fun insertCheckList(data:CheckListModel)
#Insert
fun insertCheckListPoint(vararg data:CheckListPoints)
#Delete
fun deleteCheckList(checkList: CheckListModel)
#Transaction
#Query("SELECT * FROM check_list_model " )
fun getEverything(): Flow<List<CheckListWithCheckListModel>>
}
and this is how i add
private var doorCheckListModel = CheckListModel("Дверь",0,0,"4321")
private val doorCheckListPoint1 = CheckListPoints(false,"1",false,doorCheckListModel.checkListModelID)
private val doorCheckListPoint2 = CheckListPoints(false,"2",false,doorCheckListModel.checkListModelID)
private var doorListOfCheckListPoints = listOf<CheckListPoints>(doorCheckListPoint1,doorCheckListPoint2)
private var windowCheckListModel = CheckListModel("Окно",0,0,"4321")
private var windowCheckListPoint1 = CheckListPoints(false,"1",false,windowCheckListModel.checkListModelID)
private var windowCheckListPoint2 = CheckListPoints(false,"1",false,windowCheckListModel.checkListModelID)
private var windowListOfCheckListPoints = listOf<CheckListPoints>(windowCheckListPoint1,windowCheckListPoint2)
var checkLists = MutableLiveData<List<CheckListModel>>().apply {
value = listOf(doorCheckListModel,windowCheckListModel)
}
fun addCheckList(name: String){
viewModelScope.launch(Dispatchers.IO) {
when (name) {
"Дверь" -> insert(doorCheckListModel,doorListOfCheckListPoints)
"Окно" -> insert(windowCheckListModel,windowListOfCheckListPoints)
}
}
}
private suspend fun insert(checkList: CheckListModel, checkListPoints: List<CheckListPoints>){
database.insertCheckList(checkList)
for(checkListPoint in checkListPoints){
database.insertCheckListPoint(checkListPoint)
}
}
}
also i display data from CheckListModel in fragment. CheckListModel added to DB Correctly and display correctly, but CheckListPoints has not
When you create doorCheckListModel, its checkListModelID is initially 0. You use this 0 as checkListColumnID in doorCheckListPoint1. So when you save the CheckListModel, Room automatically generates the primary key and saves in the table. Similar is the case for primary key in CheckListPoints table. But the entries saved in CheckListPoints table still have 0 in checkListColumnID column.
This is why the foreign key constraint is failing. There is no CheckListModel with 0 as its primary key. To fix this, you will have to set the value of checkListColumnID before saving a CheckListPoints entry in the table.
If you go through Room documentation, the #Insert annotated function can optionally return the rowId for the inserted item. For integer primary keys, rowId is the same as primary key.
Try this code:
// Return the primary key here
#Insert
fun insertCheckList(data:CheckListModel): Long
private suspend fun insert(checkList: CheckListModel, checkListPoints: List<CheckListPoints>){
val id = database.insertCheckList(checkList)
for(checkListPoint in checkListPoints){
database.insertCheckListPoint(checkListPoint.copy(checkListColumnID = id))
}
}

Create instance of class kotlin

How can I create an instance of InfoA that contains also title. Do I need to modify the classes?
Can't specify the title.
Also, do I need to create setters for it? To not access with the _
val info = InfoA(_subtitle = "SUBTITLE", title = ...)
open class Info(
open val action: Action = Action(),
open val title: String? = ""
) {
fun hasAction(): Boolean = action.hasAction()
}
class InfoA(
private val _subtitle: String? = "",
private val _image: String? = "",
private val _backgroundImage: String? = "",
private val _backgroundColor: String? = null,
private val _foregroundColor: String? = null,
private val _borderColor: String? = null
) : Info() {
val subtitle: String
get() = _subtitle.orEmpty()
val image: String
get() = _image.orEmpty()
val backgroundImage: String
get() = _backgroundImage.orEmpty()
val backgroundColor: Int?
get() = if (_backgroundColor != null) convertRgbStringToColorInt(_backgroundColor) else null
val foregroundColor: Int?
get() = if (_foregroundColor != null) convertRgbStringToColorInt(_foregroundColor) else null
val borderColor: Int?
get() = if (_borderColor != null) convertRgbStringToColorInt(_borderColor) else null
}
As the code is written, title is a val, so it can't be changed from its initial value — which is empty string if (as in the case of InfoA) something calls its constructor without specifying another value.
If it were changed to be a var, then it could be changed later, e.g.:
val info = InfoA(_subtitle = "SUBTITLE").apply{ title = "..." }
Alternatively, if you want to keep it a val, then InfoA would need to be changed: the most obvious way would be to add a title parameter in its constructor, and pass that up to Info:
class InfoA(
title: String? = "",
// …other fields…
) : Info(title = title) {
Note that this way, InfoA can never use Info's default value for title, so you may need to duplicate that default in InfoA's constructor.
The need to duplicate superclass properties in a subclass constructor is awkward, but there's currently no good way around it.  (See e.g. this question.)  If there are many parameters, you might consider bundling them together into a single data class, which could then be passed easily up to the superclass constructor — but of course users of the class would need to specify that.  (Some people think that having more than a few parameters is a code smell, and that bundling them together can often improve the design.)

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