Home > Software design >  Kotlin error Smart cast to 'X' is impossible, because 'state' is a property that
Kotlin error Smart cast to 'X' is impossible, because 'state' is a property that

Time:10-14

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
}

CodePudding user response:

This line:

val userState by userViewModel.userState.collectAsState()

Defines userState through a delegate, so the compiler cannot guarantee that the access in the when() and the access within the when's branch will return the same value.

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
}

CodePudding user response:

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
}
  • Related