Getters cannot be used to identify return type properly in Kotlin - kotlin

I have a data class that has the following form:
data class ContentElementField(val type: String) {
val text: String? = null
get() = requireNotNull(field)
val style: String? = null
get() = requireNotNull(field)
val path: String? = null
get() = requireNotNull(field)
val caption: String? = null
get() = requireNotNull(field)
}
The problem arises when I want to perform the following operation:
when (it.type) {
"text" -> TextElement(Text(it.text), Style(it.style))
"image" -> ImageElement(Path(it.path), Caption(it.caption))
}
The compiler warns me about that You cannot send a nullable type to a function that does not accept nullable arguments.
Even if the field is signed to be nullable, its getter is signed to be not nullable, though.
The compiler should use getters to resolve whether to give this warning.
What would you offer to get around this problem?

It doesn't matter if your getter happens to crash if the current value is null - the type is still nullable, the getter's return type is still String?.
Why are you doing this anyway? Why not just make the fields non-null as normal and let a null assignment throw the exception instead? That way you won't have to fight the type system.
If what you have in mind is different and this was just meant to be a simple example, then you have a few options:
Use !! at the call site since you're guaranteeing it's not null
"text" -> TextElement(Text(it.text!!), Style(it.style))
Expose the private nullable property through a non-null one:
// I see people do this a lot in Activities and Fragments even though
// they should probably just be making the one property lateinit instead
private val _text: String? = whatever
val text: String get() = requireNotNull(_text)
Maybe look at Kotlin contracts which allow you to make guarantees to the compiler about values (no example because I've never used it)
It's not really clear what you actually want to do though, or why this is useful. Your example is even using vals and assigning null to them. Whatever your real use case is, there's probably a better way.
(Also in case you're not aware, properties that aren't constructor parameters aren't included in the basic data class behaviour, i.e. its equals/hashCode/toString implementations. Another reason just making the types non-null helps, you can stick them in the constructor instead of having to do this logic)

Related

Different results on similar code with safe call operator in Kotlin

I'm new to Kotlin and these two below codes give different results.
fun main() {
var name: String? = "Rajat"
name = null
print(name?.toLowerCase())
}
Output: Compilation Error (illegal access operation)
fun main() {
var name: String? = null
print(name?.toLowerCase())
}
Output: null
When you do this assignment:
name = null
name is smart casted to Nothing?, which is problematic. Nothing is the subtype of every type, and so you become able to call any accessible extension functions of any type, according to the overload resolution rules here.
Compare:
fun main() {
var name: String? = "Denis"
name = null
print(name?.myExtension()) // works
val nothing: Nothing? = null
print(nothing?.myExtension()) // also works
}
fun Int.myExtension(): Nothing = TODO()
Note that allowing you to call any extension function on Nothing is perfectly safe - name is null anyway, so nothing is actually called.
Char.toLowerCase and String.toLowerCase happen to be two of the extension functions that are accessible, and you can call both on name, which is now a Nothing?. Therefore, the call is ambiguous.
Note that smart casts only happens in assignments, not in initialisers like var name: String? = null. Therefore, name is not smart casted to Nothing? in this case:
fun main() {
var name: String? = null
print(name?.toLowerCase()) // better to use lowercase(), toLowerCase is deprecated!
}
For the reason why, see my answer here.
The actual error on your first example is
Overload resolution ambiguity: public inline fun Char.toLowerCase(): Char defined in kotlin.text public inline fun String.toLowerCase(): String defined in kotlin.text
Looks like the Kotlin compiler is being too smart for its own good here. What's happening, is that on the second example, you are explicitly defining a variable of type String? and assigning it some value (null in this case, but that doesn't matter).
On the second example, you are defining a variable of some type, and then telling the compiler "hey, after this assignment, name is always null". So then it remembers the more-specific "name is null" instead of "name is String?".
The standard library has two methods called toLowerCase, one on Char and one on String. Both of them are valid matches now, and the compiler is telling you it doesn't know which one to pick. In the end that won't matter, because name is null, but the compiler apparently doesn't use that final thing to throw out the method call altogether.

Kotlin error Smart cast to 'X' is impossible, because 'state' is a property that has open or custom getter when trying to observe state

I'm try to observe state as you see but when i use when and try to get data, compiler says Smart cast is impossible by casting it solves the problem but It felt like i'm doing it in wrong way, i want to know there is any other solution to fix this error.
sealed class Response<out T : Any> {
object Loading : Response<Nothing>()
data class Success<out T : Any>(val data: T) : Response<T>()
data class Error(val error: ResultError, val message: String? = null) : Response<Nothing>()
}
val userState by userViewModel.userState.collectAsState()
when(userState){
is Response.Error -> userState.error // Smart cast to 'Response.Error' is impossible, because 'userState' is a property that has open or custom getter
Response.Loading -> Unit
is Response.Success -> userState.data // Smart cast to 'Response.Success<User>' is impossible, because 'userState' is a property that has open or custom getter
}
This line:
val userState by userViewModel.userState.collectAsState()
Defines userState through a delegate, so the compiler cannot guarantee that subsequent reads of the property's value will give the same value. In particular here, it means the access in the when() condition and the access within the when's branches might not return the same value from the compiler's point of view, thus it cannot smart cast.
You could use an intermediate variable here:
val userState by userViewModel.userState.collectAsState()
when(val s = userState){
is Response.Error -> s.error
Response.Loading -> Unit
is Response.Success -> s.data
}
Now since s is a local val the compiler can guarantee it will have the same value in the condition and in the when branches, and smart casting works
Compiler can only perform smart casts when it can guarantee that the value won't change with time. Otherwise, we might get into the situation where after the type check the variable changed to another value and does no longer satisfy the previous constraint.
Delegated properties (ones declared with by keyword) are much different than "normal" variables. They don't really hold any value, but each time we access them, we actually invoke getValue() (or setValue()) on their delegate. With each access the delegate may provide a different value. Compiler can't guarantee immutability of the value and therefore smart casts are disallowed.
To fix this problem, we need to create a local copy of the data that is delegated. This is like invoking getValue() and storing the result as a local variable, so it can no longer change. Then we can perform smart casts on this local data copy. It can be understood better with the following example:
fun main() {
val delegated by Delegate()
println(delegated) // 0
println(delegated) // 1
println(delegated) // 2
val local = delegated // `local` set to 3
println(local) // 3
println(delegated) // 4
println(local) // 3
}
class Delegate {
var i = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return i++
}
}
Each time we access delegated it returns a different value. It may change between null and not null or even change the type entirely. When we assign it to local we take "current" value of delegated and store its copy locally. Then delegated still changes with each access, but local is constant, so we can perform smart casts on it.
Depending on your case, if there is a way to acquire "current" or "direct" value of userViewModel.userState.collectAsState() then you can use it when assigning to userState - then it should work as you expect. If there is no such function, then I think the easiest is to use another variable to store a local copy, like this:
val _userState by userViewModel.userState.collectAsState() // delegated
val userState = _userState // local copy, immutable
when(userState){
is Response.Error -> userState.error // Smart cast to 'Response.Error' is impossible, because 'userState' is a property that has open or custom getter
Response.Loading -> Unit
is Response.Success -> userState.data // Smart cast to 'Response.Success<User>' is impossible, because 'userState' is a property that has open or custom getter
}

How to modify the parameter pass to kotlin data class?

Say I have a data class
data class MyClass(val crop: Rect, val name: String)
But I want to make a copy of the Rect passed in since I don't want the value to be modified later. I don't want to the caller to call
MyClass(Rect(inCrop), "name")
in the code. How can I do this in my data class?
Thanks.
One workaround I can think of is:
data class MyClass(private var privateCrop: Rect, val name: String) {
val crop get() = privateCrop
init {
privateCrop = Rect(privateCrop)
}
}
You make crop private and make it a var (privateCrop), then you add a public getter for it. Now you can copy it in an init block.
But I gotta admit, this is rather ugly. The better solution here I think is to change Rect to be immutable, but if Rect isn't in your control, then I guess it can't be helped. You might also consider using a regular class.
You may not want to alter data class's like this. As per another solution's answer, you may find other peculiarities with this solution. The solution given by #Sweeper, also does not include providing a defensive copy, which you may want to do to avoid access to modifying the internal property field.
To quote:
After spending almost a full year of writing Kotlin daily I've found that attempting to override data classes like this is a bad practice. There are 3 valid approaches to this, and after I present them, I'll explain why the approach other answers have suggested is bad.
Have your business logic that creates the data class alter the value to be 0 or greater before calling the constructor with the bad value. This is probably the best approach for most cases.
Don't use a data class. Use a regular class and have your IDE generate the equals and hashCode methods for you (or don't, if you don't need them). Yes, you'll have to re-generate it if any of the properties are changed on the object, but you are left with total control of the object.
class Test(value: Int) {
val value: Int = value
get() = if (field < 0) 0 else field
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Test) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
Create an additional safe property on the object that does what you want instead of having a private value that's effectively overriden.

Arrow lens won't let me set a nullable property to null

Given this (extremely simplified) code :
#optics
data class MigrationStatus(val token: String?)
val m = MigrationStatus(null)
I can call
val m1 = MigrationStatus.token.modify(m) { "some token" }
But as the argument type is a non nullable String, how can I modify token back to null ? Of course
val m2 = MigrationStatus.token.modify(m1) { null }
does not compile.
The same happens when changing token type to an Option<String> when trying to set it to None, but I avoided it as it is now deprecated (which I'm not sure I like, but that's another matter).
Did I miss something obvious ?
The #Optics compiler generates 2 optics for that property.
MigrationStatus.token & MigrationStatus.tokenNullable or MigrationStatus.tokenOption in the case of Option.
This is because there are two different Optics that are useful here.
Lens which has set & get and in this case `Lens<MigrationStatus, String?>
Optional which has set & getOption and in this case `Optional<MigrationStatus, String>
The first one is the one you'd want to use in this case to be able to set String? to null.
So MigrationStatus.tokenNullable.set(null).
The latter is more useful for the DSL, and composition of Optics since if instead of String? you had another data class or sealed class you'd want to operate on the value only in the case of not-null.
I hope that fully answers your question!

Kotlin: Intrinsics.areEqual infinite loop (stack overflow)

java.lang.StackOverflowError
at kotlin.jvm.internal.Intrinsics.areEqual(Intrinsics.java:164)
at plugin.interaction.inter.teleports.Category.equals(Category.kt)
at kotlin.jvm.internal.Intrinsics.areEqual(Intrinsics.java:164)
at plugin.interaction.inter.teleports.Destination.equals(Destination.kt)
Happens from a .equals comparison between two non-relationship data classes.
Major bug.
data class Category(val name: String, val destinations: MutableList<Destination>)
data class Destination(val category: Category, val name: String)
Data classes in Kotlin are just syntactic sugar for Java POJOs.
The main culprit in your example is this cycle:
val destinations: MutableList<Destination> in Category &
val category: Category in Destination
You must remove this cycle by moving either of the two variables out of the primary data class constructor.
However, there is also a much bigger sideeffect: data class Category(..) is mutable, which will cause for it (and any other data class using categories in it's primary constructor!) to be unsafe to use as keys in any hash-based collection. For more information, see: Are mutable hashmap keys a dangerous practice?
Given that data classes are meant for pure data, I recommend removing val category: Category in data class Destination(..), and change type of val destinations: MutableList<Destination> in data class Category(..) to read-only List<Destination>. In order to break immutable state after said changes, you will have to either perform unsafe casts from Kotlin or create an instance of the class from Java.
If you however absolutely require a backreference to categories in destinations (and aren't using your classes in hashmaps/-sets/etc.), you could either make Destination a regular class and implement equals/hashCode yourself, or move the category out of the primary constructor. This is a bit tricky, but can be done with a secondary constructor:
data class Destination private constructor(val name: String) {
private lateinit var _category: Category
val category get() = _category
constructor(category: Category, name: String) : this(name) {
_category = category
}
}
Well in my case I was overriding equals method like:
override fun equals(other: Any?): Boolean {
// some code here
if (other==this)
return true
// some code here
}
equals and == in java
In java when we use equals(for ex: str1.equals(str2)) it checks the content of two object(for custom objects you must have to override equals and check all the values of objects otherwise Object class's equals method just compare reference, which is same as ==), but if we use ==(for ex: str1==str2) operator, it checks the reference of both objects.
== in kotlin
But in case of kotlin when we use == operator, it checks the content(data or variable) of objects only if they are object of data class. And == operator checks reference for normal class.
when we use == it will call the equals method.
So in my overridden equals method when other==this will execute it will call eaquals method again, and that will call eaquals method again and make an infinite loop.
So to make it work we need to change == to ===(this will check the reference of both operator), like:
if (other===this)
return true
Note: .equals and == are same until we use them with Float or
Double. .equals disagrees with the IEEE 754 Standard for
Floating-Point Arithmetic, it returns a false when -0.0 was compared
with 0.0 whereas == and === returns true
You can check reference here