Field Value Changes
Specific field value changes can be observed
Obtaining the field
The reference to the FormBox for the field can be obtained using the field function of the FormController,
both in a Composable, and at the ViewModel layer. This takes a KProperty (as with the modelProperty) field,
which identifies the Form Field to obtain.
Subscribing using an Effect in composables
To perform an action in a Composable when the value of the form changes, use FieldValueChangedEffect
This works in a similar way to LaunchedEffect and as such receives a context parameter to alter the scope
in which the observation takes place. Also similar to LaunchedEffect, subscriptions will be cancelled when this
Composable leaves the composition.
Using field value changes
The new value of the field will be provided to the function, and this function will be called whenever the value changes.
Debounce
It is common that when a user is typing, the value may change very quickly.
In these cases, to avoid the block being called and cancelled repeatedly (note this uses Flow.collectLatest behind the scenes)
a debounce parameter is provided.
Where this is specified as >0, the block will only be called this number of milliseconds after the last value change.
This allows, for example to only execute a network call using the value once the value has settled.
Example
FormTextField(
modelProperty = FormTextFieldExampleModel::value,
validator = NumberOnlyValidator(),
updateModel = { value = it },
) {
FieldValueChangedEffect {
if(text.isNotEmpty()){
validate()
}
}
DefaultTextEntry(hint = "Value")
}
Subscribing in the ViewModel, or other asynchronous contexts
The suspend function onFieldValueChanged is provided to allow subscriptions outside composable contexts.
Similar to the FieldValueChangedEffect, a debounce is provided to allow the value to settle before observing.
The value is provided as the first parameter in the callback.
One example application of this is an address search field. The ViewModel can observe the value changes, and then send off an API request to fetch addresses based on the search field value. Finally results can be provided back to the UI via the state.
Note for this to be most effective, two recommendations are provided:
- The
onFieldValueChangedfunction should be called in the viewModelScope, to ensure subscriptions are cancelled once the ViewModel is destroyed. - The ViewModel should implement the
FormControllerViewModelto avoid having to pass theFormControllerfrom the UI to theViewModel
Example
class MyFormViewModel(
val addressService: AddressService
): FormControllerViewModel(){
val uiState: MutableStateFlow<List<AddressItem>> = MutableStateFlow(emptyList())
init {
subscribeToAddressChanges()
}
fun subscribeToAddressChanges() = viewModelScope.launch {
field(MyModel::address).onFieldStringValueChanged(debounceMillis = 300) {
uiState = addressService.search(it)
}
}
}
Transforming the value before observation
The value can also be observed via the valueFlow field
Then, standard Flow operators can be used, such as:
mapLatestto transform the valuedebounceto allow values to settlecombineto merge the value with other running flowsfilterto only process particular values