Doubt regarding kotlin contracts: I'm just trying...
# android
v
Doubt regarding kotlin contracts: I'm just trying to make smart casting with kotlin contracts. But it is not working. My code is as below...
Copy code
sealed class UiState {
    data class Success(val data: String): UiState()
    data class Error(val message: String): UiState()
    object Loading: UiState()
}

@OptIn(ExperimentalContracts::class)
fun UiState.isSuccess(): Boolean {
    contract {
        returns(true) implies(this@isSuccess is UiState.Success)
    }

    return this is UiState.Success
}

class SampleViewModel: ViewModel() {

    var state: UiState by mutableStateOf(UiState.Loading)

    init {
        state = UiState.Success("Initial data")
    }

    fun doSomething() {
        if (state.isSuccess()) {
            state = state.copy(data = "New data")
        }
    }
}
In this in the
doSomething
function I'm reassigning state with a new state value. Here in the previous line i have called an extension function isSuccess which has a contract that implies the state is Success. But in the next line it is not able to smart cast. Am i missing something?... My expectation here is it to be smart casted to Success state. Ref: https://kt.academy/article/ak-contracts#implications-of-the-fact-that-a-function-has-returned-a-value
🧵 2
m
Smart casting can never be done on a
var
property. And then even if it was a val, it would still fail because you are getting it from a property delegate. Basically the compiler cannot guarantee that the getter for
MutableState
will not change what it returns between the call to
isSuccess
and the call to
copy
.
v
1. Smart casting can neven be done on a
var
property. Let's take the below example,
Copy code
fun printEachLine() {
    var list: MutableList<String>? = mutableListOf()
    if (!list.isNullOrEmpty()) {
        for (e in list) { 
            println(e)
        }
    }
}

@OptIn(ExperimentalContracts::class)
inline fun <T> Collection<T>?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.isEmpty()
}
Here the list is var.... But since, the isNullOrEmpty has the contract that implies that the given collection is not null if it returns true... So, there is no compilation error. if we remove the contract in the isNullOrEmpty method, then it will throw the error. 2. Property delegate. Here is another sample(in the image attached) in which i used State with
val
and didn't use property delegate But still it is not smart casting.
m
Local vars can be smart casted but not class level. Yes not using property delegates still doesn't allow smart casting, because the
value
property in the
MutableStateList
is a
var
. Even if it was a
val
you still could not smart cast, because the Kotlin compiler does not assume that
val
properties from other
jar
or
klib
are read only, because they could be changed in the future to not be readonly, without recompiling your code.
👍 1
v
Ok... Great!! Thanks for your explanation!
l
Thank for this document @Michael Krussel, it explains everything. I’m usually see this kind of smart cast does not work:
Copy code
class ExampleViewModel: ViewModel (){
   val _uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState.Idle)
   val uiState = _uiState.asStateFlow()
}

@Composable
fun ExampleComposable(viewModel: ExampleViewModel){
  
  val uiState by viewModel.uiState.collectAsState()
}
I have to use val in size when again
Copy code
when (val state = uiState){ 
   UiState.Loading -> {
     ///
   }
}