I have a `when` block, where I assert on propertie...
# getting-started
l
I have a
when
block, where I assert on properties of a type from a library being not null (so they cannot be smart-cast). I don't want to use
!!
inside the
when
cases, I also don't want to use
?.let { ... }
everytime. Right now I solved it, that I assign every property to a local variable before the when block:
Copy code
val result = ... // Result is a type from external library
// Assigning result properties to local variables, so they can be smart cast
val error = result.error
val product = result.product
// There are more properties

when {
    error != null -> handleError(error) // Smart cast here to non-nullable error
    product != null -> handleProduct(product) // same
    // ...
}
Is there some other approach to get around this limitation?
s
The
?.let
approach seems like the most idiomatic. What's the reason you don't want to write it that way?
l
It looks a bit weird, when I just asserted for the value being not null, and directly afterwards making
?.let
on it. Also it adds a level of nesting. More importantly, some of the
when
cases check multiple values in combination, e.g.
Copy code
when {
    error != null && product != null -> // Do something with error and product
    // ...
}
s
Oh, I was thinking of just using
let
without the
when
. But that still wouldn't make it any easier when using multiple values.
Can you have an error at the same time as a product, or is it always one or the other?
l
Yes, a product can have an error
👍 1
s
But when you handle it, you only handle the first non-null property, and ignore the rest? That's how it looks from the code you shared, at least
l
Yes, the example was somewhat limited
Copy code
when {
    error != null -> {
        with(error) {
            if (ERROR_CODES_THAT_TRIGGER_REMINDER.contains(code)) {
                CartRepository.instance.showReminderToScanProductsAtCashRegister = true
            }
            handleScanError(TextString(message))
        }
    }
    fractionalQuantityInfo != null && !products.isNullOrEmpty() -> {
        handleFractionalQuantityProduct(
            products.first().toUi(scannedCode.type),
            fractionalQuantityInfo,
        )
    }
    !products.isNullOrEmpty() -> handleProducts(
        products.map { it.toUi(scannedCode.type) },
    )
    voucher != null -> handleVoucher(voucher)
}
All of the values come from the external library
I think the best way would be for the external library to return a sealed class, where every case would be a single subclass
But this is a REST API, so it might be more complicated on their side (i.e. polymorphic response)
r
You could use contracts and write your own check function like this:
Copy code
fun something() {
        val result = ...
        val error = result.error
        val product = result.product

        when {
            isNotNull(result.error) -> handleError(result.error) 
            isNotNull(result.product) -> handleProduct(result.product)
            // ...
        }
    }

    @OptIn(ExperimentalContracts::class)
    private fun isNotNull(any: Any?): Boolean {
        contract { returns(true) implies (any != null) }
        return any != null
    }
l
Thanks, however this still doesn't lead to a smart cast inside the respective
when
cases.
Compiler doesn't know about this function
r
On my machine with Kotlin 2.0.21 it smart casts the fields. At least IntelliJ shows it like this:
l
Hmm ok
I'm on Android Studio 2024.2.1
Good to know, thank you!
👍 1
(I'm on Kotlin 2.1.0
k
See smart cast prerequisites:
[A smart cast on a val property can only be used] if the check is performed in the same module where the property is declared
You can't use a smart cast because
result
is an instance of a class declared in an external library. This makes sense because, as far as the compiler can see, there is no guarantee that successive invocations of this
val
property will return the same value.
l
Yes, thanks. I know why smart cast is impossible, I was wondering what the best ways to get around this problem are.
k
I realise that. I was pointing out that Ronny's solution can't possibly work no matter what contracts you try.
l
True.
It doesn't guarantee non-nullability.
So only assigning to some kind of local variable or converting to a type from the same module is actually a correct solution.
k
Yes, and doing the idiomatic
result.error?.let { ... }
for example, also introduces a local variable of sorts ("it").
👍 1
l
Thank you for the detailed explanation!