https://kotlinlang.org logo
#getting-started
Title
# getting-started
k

kotlinforandroid

06/25/2022, 3:20 PM
Is there a way to link two field states together?
Copy code
class FormField {
    var hasError: Boolean by mutableStateOf(false)
    var errorMessage: String? by mutableStateOf(null)

    // errorMessage != null iff. hasError == true
}
I tried contracts but one cannot reference variables that are not part of the contract method. I can't reason about
this@FormField.errorMessage
inside the implies boolean expression.
y

Youssef Shoaib [MOD]

06/25/2022, 3:35 PM
If it is also the exact other way around you could do
Copy code
val hasError get()= errorMessage != null
2
Or a
sealed class Error
Like this:
Copy code
sealed class ErrorStatus {
  object NoError: ErrorStatus
  class Error(val message: String): ErrorStatus
}
k

kotlinforandroid

06/25/2022, 4:15 PM
It is more about doing something like this in general. I guess in my example one could also use
derivedStateOf
. But what if the evaluation is happening another way? For example
Copy code
fun interface Transform<in T, out R> {
    fun transform(value: T): R
}

class Value<T, R>(
    initialValue: T,
    transformer: Transformer<T, R>,
) {
    var value: T

    fun evaluate(): R {
        val result = transformer.transform(value)
        validateEvaluate(result)
        return result
    }

    @ExperimentalContracts
    private fun validateEvaluate(value: R): R {
        contract {
            returns() implies (value != null)
        }
        if (value == null) throw RuntimeException()
    }
}

// Usage
class Test {
    val value: Value<String?, Float?> = Value(
        initialValue = "1.0",
        transformer = { it?.toFloat() }
    )

    fun test() {
        // At this point, even with a type of `Float?` the value can only be a `Float`.
        val float: Float = value.evaluate()
    }
}
I'd argue that the value inside of the test method can only be non-null since T is in and R is out exclusively. However, my IDE still wants me to use
!!
.
y

Youssef Shoaib [MOD]

06/25/2022, 7:57 PM
In this case, Definitely-non-nullable types should help (playground):
Copy code
import kotlin.contracts.*
fun interface Transformer<in T, out R> {
    fun transform(value: T): R
}

class Value<T, R>(
    initialValue: T,
    val transformer: Transformer<T, R>,
) {
    var value: T = initialValue

    fun evaluate(): R & Any {
        val result = transformer.transform(value)
        validateEvaluate(result)
        return result
    }

    @OptIn(kotlin.contracts.ExperimentalContracts::class)
    private fun validateEvaluate(value: R) {
        contract {
            returns() implies (value != null)
        }
        if (value == null) throw RuntimeException()
    }
}

// Usage
class Test {
    val value: Value<String?, Float?> = Value(
        initialValue = "1.0",
        transformer = { it?.toFloat() }
    )

    fun test() {
        // At this point, even with a type of `Float?` the value can only be a `Float`.
        val float: Float = value.evaluate()
    }
}
k

kotlinforandroid

06/26/2022, 12:16 PM
@Youssef Shoaib [MOD] is it possible to make this work with compose? Seems like it's only enabled for kotlin 1.7.0. and even compose 1.2.0-rc02 is only working with 1.6.21
y

Youssef Shoaib [MOD]

06/26/2022, 12:22 PM
Make it so R has an upper bound of Any, as in:
Copy code
class Value<T, R: Any>(initialValue: T, val transformer: Transformer<T, R?>) {...}
1
2 Views