Skip to content

Computed values

KForm supports the definition of schemas whose values must be the result of a provided computation. Such schemas are called “computed schemas”, instances of ComputedSchema and the values they represent are named “computed values”, instances of ComputedValue.

Computed values are part of a form’s model. As such, in a client application, they are managed by a form manager, who is responsible for updating their value based on the defined computation.

Computed schemas further contain an implicit validation which forbids the value they represent from differing from the result of the computation. I.e. forms containing computed schemas cannot be submitted unless the values represented by said schemas match the result of their computations.

Let’s take a look at an example of how to define a computed value. In the precious sections, we defined the model and schema of a form for purchasing bus tickets. Let us suppose that our form should now also include the total price to pay for the trip:

Model of a form for purchasing bus tickets with a total price
data class BusTripForm(
    var email: String,
    var passengers: Table<Passenger>,
    var totalPrice: Double,
)

data class Passenger(var name: String, var age: Int?)

If we say that the total price to pay is a fixed amount per person, we can define a computed value for computing the total amount with the following definition:

Computed total price of a bus trip
object TotalPrice : ComputedValue<Double>() {
    private const val PRICE_PER_PASSENGER = 10.0
    private val ComputedValueContext.passengers: Table<Passenger> by dependency("/passengers")

    override suspend fun ComputedValueContext.compute(): Double =
        passengers.size * PRICE_PER_PASSENGER
}

Finally, we can declare the totalPrice field as a computed schema whose computation is defined by TotalPrice:

Schema of a form for purchasing bus tickets with a total price
val BusTripFormSchema = ClassSchema {
    BusTripForm::email { StringSchema(/*…*/ ) }
    BusTripForm::passengers {
        TableSchema(/*…*/ ) {
            ClassSchema {
                Passenger::name { StringSchema(/*…*/ ) }
                Passenger::age { NullableSchema(/*…*/ ) { IntSchema(/*…*/ ) } }
            }
        }
    }
    BusTripForm::totalPrice { ComputedSchema(TotalPrice) { DoubleSchema() } }
}

Computed values vs validations

As you might have noticed, the way of defining a computed value is very similar to the way of defining a validation. In fact, they both extend the same Computation interface. The main differences between both are:

  • The class being extended: ComputedValue instead of Validation.
  • The type of the computation context: ComputedValueContext instead of ValidationContext. The main difference being that there’s no notion of “current value” in a computed value since that’s what’s actually being computed.
  • The method to implement: compute instead of validate. This method should return the computed value itself.
  • There’s no dependsOnDescendants property available, as it would be nonsensical (we cannot depend on what is currently being defined).

Aside from these differences, computed values are able to declare dependencies on other values or external contexts in the same way validations do. For reference, check the following validations’ documentation:

Built-in computed values

KForm offers the following built-in computed value:

  • SumOf: a computed value which computes the sum of all values in a column of a table. E.g. summing the ages of all passengers:

    SumOf<Int>("/passengers", Passenger::age)