I am trying convert ApiEmployee to Employee and have written a test around it. I am confused about nulls in Kotlin as I am new to it.
ApiEmployee would be used for JSON conversion so it can have missing name field or or empty or can come as null. In that case, I don't want to add into list and safely ignore it.
I am getting Method threw 'kotlin.KotlinNullPointerException at exception. at apiEmployee.name!!.isNotBlank()
ApiEmployee
data class ApiEmployee(val image: String? = "image",
val name: String? = "name test",
val description: String? = "",
val id: String? = "")
Employee
data class Employee(val imagePath: String, val id: String)
EmployeeConverter(converts ApiEmployee to Employee)
fun apply(apiEmployees: List<ApiEmployee>): List<Employee> {
val employees = mutableListOf<Employee>()
for (apiEmployee in apiEmployees) {
if (apiEmployee.name!!.isNotBlank()){
employees.add(Employee(apiEmployee.image!!, apiEmployee.id!!)
}
}
}
EmployeeConverterTest
#Test
fun `should not add employee without name into employee list`() {
val invalidApiEmployee = ApiEmployee("image", null, "description", "id")
val convertedEmployees : List< Employee > = employeeConverter.apply(listOf( invalidApiEmployee))
assertThat(convertedEmployees.size).isEqualTo(0)
}
What you want to do is check if the name is null first and then if it is empty.
val employeeNameIsNotEmpty = apiEmployee.name?.isNotBlank() ?: false
if (employeeNameIsNotEmpty) {
// do stuff
}
The apiEmployee.name?.isNotBlank() will run and return a value only if name is not null. If name is null then the statment on the right side of ?: will return its value, which in this case should be false.
In this case however Kotlin has already put this particular example into an extension function
.isNullOrBlank()
So you could change it to:
if (!apiEmployee.name.isNullOrBlank()) {
// do stuff
}
As a side note you really don't whant to do this Employee(apiEmployee.image!!, apiEmployee.id!!).
Because image and id could still be null and crash your code with the same error.
Either pass the value for name.
ApiEmployee("image", "name", "description", "id")
(or)
Change the if condition as mentioned below (with ? operator):-
if (apiEmployee.name?.isNotBlank()){
?. performs a safe call (calls a method or accesses a property if the
receiver is non-null)
!! asserts that an expression is
non-null
The code asserts that name is not null and checking for not blank.
Probably, I think you are trying to do null and not blank check. You can use ? operator (safe call) for that. This means isNotBlank() gets executed only if the name is not null.
Related
I am trying to filter a list based on a condition that a property inside the list is an enum type. But I get an error on the filter function. Can anyone tell me how to resolve this error and why it is happening?
Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
My code is below:
data class Person(
val name: String,
val ageInDays: Int,
val currentStatus: List<Status>,
)
data class Status(
val name: String,
val activity: Activity
)
enum class Activity {
COOK,
CLEAN,
SLEEP,
}
fun main() {
var build = listOf(
Person("abc", 3655, listOf(
Status("abcProc1", Activity.COOK),
Status("abcProc2", Activity.CLEAN),
Status("abcProc2", Activity.SLEEP),
)
),
Person("ghi", 500, listOf(
Status("ghiProc", Activity.COOK),
Status("ghiProc", Activity.SLEEP),
)
),
Person("def", 1000,listOf(
Status("defProc", Activity.SLEEP)
)
)
)
println(build.filter { it.currentStatus.contains(Activity.CLEAN) })
}
currentStatus is a List<Status>. The only type of object that could be in the list is a Status (or subtype thereof). So it doesn't make sense to call contains on the list with an argument that is not a Status. An Activity is not a subtype of Status.
Assuming you want to filter your list of Status to only include instances of Status for which the activity property is Activity.CLEAN, you would do it like:
build.filter { it.currentStatus.any { status -> status.activity == Activity.CLEAN } }
or slightly less efficient but possibly clearer logic:
build.filter { it.currentStatus.map(Status::activity).contains(Activity.CLEAN) }
The following example is a simplified version of what I am trying to do in my application.
fun main() {
val test: MutableMap<Int, String> = mutableMapOf(
1 to "Apple",
)
test[2] = test[1] // test[1] has incorrect type.
}
The code doesn't compile. IntelliJ gives the following hint:
Type mismatch.
Required: TypeVariable(V)
Found: String?
I don't understand what a TypeVariable is. but when I provide a default value the error disappears
test[2] = test[1] ?: "Grape"
Why the required type is TypeVariable(V), not String, and what exactly is it? What to do if there's no default value for my application purposes?
... = test[1]
returns a String? as the hint showed you. But test is a MutableMap of <Int, String>, which means you need to assign a String:
... = test[1]!!
Of course this will only work if 1 is a valid key in test. Otherwise your code with the null safety operator is the way to go:
... = test[1] ?: "default value"
I have a list of objects with an optional id as String and I want to make a map out of it.
I want to have the keys of my map as non nullable: so something like this:
data class Foo(
val id: String? = null
val someStuff: String? = null,
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo.filterNot { it.id == null }.associateBy { it.id }
Here bar type is Map<String?, Foo> but not Map<String, Foo>
My workaround is to add a non null asserted call: !!, but it doesn't seem clean.
Is there an easy and safe way to do this?
This looks like something that contracts could help with, but currently a contract expression can't access properties of the class in use.
As a workaround, you could define a 2nd class that has a non-null id, like so
data class Foo(
val id: String? = null,
val someStuff: String? = null
)
data class Foo2(
val id: String,
val someStuff: String? = null
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo
.mapNotNull { if (it.id != null) Foo2(it.id, it.someStuff) else null }
.associateBy { it.id }
There's a six-year-old open feature request for Map.filterNotNullKeys() and a four-year old open feature request for Map.associateByNotNull().
In my opinion, the associateBy { it.id!! } would be cleanest for readability. But you could do it like this:
val bar = foo.mapNotNull { it.id?.run { it.id to it } }.toMap()
As for your actual question, that logic is way too many steps for the compiler to infer. Your last function call to associateBy sees a nullable, so it infers a nullable. For the compiler to figure this out, it would have to step back and see that the List that you call associateBy on happens to have filtered out certain objects in a way that happens to ensure that a certain nullable property won't be null within this specific list, and it's the same property that you are associating with. Now imagine it has to do this for every call to any generic function, and the various lambdas involved could potentially have multiple lines of code. Compile times would skyrocket.
I am kinda new using kotlin and I was wondering if I can do something like this.
I have a list with objects of type Person,
Person has properties like name, id but can be null.
So I made something like this:
return persons.filter {
it.name != null && it.id != null
}.map {
it.id to it.name
}.toMap()
I personally dont see the error but the compiler keeps telling me I should return a map of not nulls.
Is there any way I can do it using filter and map using lambdas functions?
Use mapNotNull to combine filter and map:
persons.mapNotNull {
val id = it.id
val name = it.name
if (id != null && name != null) Pair(id, name) else null
}.toMap()
Pulling id and name into local variables should make sure they'll get inferred as not-null in Pair(id, name) but may not be necessary.
The reason your approach doesn't work is that persons.filter { ... } just returns List<Person>, there's no way to say "list of Persons with non-null name and id" or to represent it internally in the compiler.
btw, you can even get rid of if in mapNotNull:
persons.mapNotNull {
val id = it.id ?: return#mapNotNull null
val name = it.name ?: return#mapNotNull null
id to name
}.toMap()
May be you can simply change
.map {
it.id to it.name
}
into
.map {
it.id!! to it.name!!
}
The suffix !! operator converts String? into String, throwing exception if the value with type String? is null, which in our case can't be true, due to the previously applied filter.
Use of !! should be limited to cases where you take responsibility of explicitly asserting that value can't be null: you're saying to the compiler that values are String even if the type is String? - and should be imho used with caution.
Compiler can't infer the type domain restriction from String? to String from the predicate passed to filter, but you can, so I think !! usage can be a valuable approach...
I have list of table column names and it's values which will be determined # run time. Right now I am using following way to achieve the feet which requires casting Filed to TableField for every single column name. Is there any better way ?
override fun updateFields(job: Job, jsonObject: JsonObject, handler: Handler<AsyncResult<Job?>>): JobQService {
val updateFieldsDsl = dslContext.update(JOB)
var feildSetDsl: UpdateSetMoreStep<*>? = null
jsonObject.map.keys.forEach { column ->
feildSetDsl = if (feildSetDsl == null) {
updateFieldsDsl.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
} else {
feildSetDsl!!.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
}
}
val queryDsl = feildSetDsl!!.where(JOB.ID.eq(job.id))
jdbcClient.rxUpdateWithParams(queryDsl.sql, JsonArray(queryDsl.bindValues)).subscribeBy(
onSuccess = { handler.handle(Future.succeededFuture(job)) },
onError = { handler.handle(Future.failedFuture(it)) }
)
return this;
}
I'm not sure what you mean by "better" but there is a method UpdateSetStep.set(Map), which seems to be helpful for what you're trying to do. See the javadoc:
UpdateSetMoreStep set(Map<?,?> map)
Set a value for a field in the UPDATE statement.
Keys can either be of type String, Name, or Field.
Values can either be of type <T> or Field<T>. jOOQ will attempt to convert values to their corresponding field's type.