Kotlin assumes value to be not null - kotlin

I have the below code to pick the first item from the list where the item's lastname field value should not be UNKNOWN or MISSING.
val userLastName = someList
.first { it.lastName != "UNKNOWN" && it.lastName != "MISSING" }
.lastName
Now Intellij says that the field userLastName can never be null. Why?
If the list has all objects whose lastName field value is either UNKNOWN or MISSING then the userLastName variable will be null right?
I tried to change the code to use null safe operator:
val userLastName = someList
.first { it.lastName != "UNKNOWN" && it.lastName != "MISSING" }
?.lastName
But I get the below warning:
Safe call on a non-null receiver will have nullable type in future releases

The .first function requires that at least one element matches the predicate. It throws an exception if no matching element is found.
If you want to instead return null when no elements match, you can use .find instead of .first.

Related

How to filter list of objects by the value of the one field in Kotlin?

I have got list of objects
listOf(User("John",32), User("Katy",15), User("Sam",43))
How can write a function which returns me the User object if in parameter I pass a name. For example getUser("John") and it suppose to return me User("John",32)
One possibility is also using firstOrNull:
val list = listOf(User("John",32), User("Katy",15), User("Sam",43))
list.firstOrNull { it.name == "John" }

How to assign a new list to a nullable field if null or else just add an element to the existing list in Kotlin?

I have an object media that holds descriptions which is a list. I'd love to see some elegant logic in Kotlin to add an element to that descriptions if the field is not null or add a fresh new list (with an initial element) to that field if it is null.
Pseudo:
if (media.descriptions == null) { media.descriptions = listOf("myValue")}
else { media.descriptions.add("myValue") }
I would probably do it the other way around, except you need to alter media itself (see below), i.e. creating your list first and add all the other entries to that list if media.descriptions isn't null:
val myDescriptions = mutableListOf("myValue") // and maybe others
media.descriptions?.forEach(myDescriptions::add)
If you need to manipulate descriptions of media, there is not so much you can do to make it more readable...:
if (media.descriptions == null) {
media.descriptions = mutableListOf("myValue") // just using mutable to make it clear
} else {
media.descriptions += "myValue"
}
or maybe:
if (media.descriptions == null) {
media.descriptions = mutableListOf<String>()
}
media.descriptions?.add("myValue")
You can use the elvis ?: operator to assign the list.
The simplest way I can think of is
media.descriptions = media.descriptions ?: listOf("myValue")
media.descriptions.add("myValue")

how can I clean my map to return Map<String, String> Instead Map<String? , String?> In kotlin?

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

How to properly sanitize a list of items received from server using RX | filter{} map{}

I have the following code which I am trying to use for two purposes:
1) Call an API and get result as a POJO
2) Sanitize this object (POJO) before I display it in the UI
private fun getWinbackDataItems(rewardPurpose: String) /*Single<WinbackBaseItem>*/ {
val x = repository.getRewardsList(rewardPurpose)
.filter {
it.result?.rewards != null
}.map { winback ->
winback.result?.rewards?.asSequence()?.filter { rewardsItem ->
rewardsItem?.id != null && rewardsItem.title != null
}?.toList()?.take(3)?.map {
WinbackListItem(it?.id, it?.title!!, false)
}?.toList()
}
}
The point of contention for me is the line below:
itemListSanitized.add(WinbackListItem(it.id, it.title, false))
At this point I assume the filter has removed all nulls from the original list but to my amazement I find that I have to null check on it and all its content while adding them to the new list.
What do I miss here, pardon my naivety as I have just begun reactive
I take it that you are working not against executing code but against your IDE's warning messages or just the ability for this code to compile. What you're probably running up against is that earlier checks for null won't necessarily allow the compiler to assume non-null values later on, because in the meantime, other code in a different thread could have run and changed the values.
So when you create a WinbackListItem, you can safely assume that certain items are not null, and yet the compiler can't be sure of this, because it can't know what else is going on in your process space. So the compiler requires that you tell it not to worry about null values (!!) or that you check the values again. This is just the way Kotlin works. It's often a PITA, but it's just how it is.
I played with the posted code just to be sure I knew what I was talking about. Here is code that I was able to run:
private fun getWinbackDataItems(rewardPurpose: String) /*Single<WinbackBaseItem>*/ {
val x = repository.getRewardsList(rewardPurpose)
.filter {
it.result?.rewards != null
}.map { winback ->
winback.result?.rewards?.asSequence().filter { rewardsItem ->
rewardsItem.id != null && rewardsItem.title != null
}.toList().take(3).map {
println(it.id)
println(it.title)
WinbackListItem(it.id!!, it.title!!, false)
}.toList()
}.count()
}
I created some very simple classes and objects to satisfy this code and let it run. Note that I took out some unnecessary '?' null checks. I played with input values until I was convinced that it.id and it.title can never be null when the WinbackListItem constructor is called. And yet, the two !! on its parameters, or something else making sure they are not null, are required given this definition of WinbackListItem that won't accept null parameter values:
class WinbackListItem(val id: Int, val title: String, val huh: Boolean)

Kotlin data classes JSON Deserialization

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.