How It Works
Overview
FormValidator(flow, fields)
│
▼
validator.validate()
│
├─ Flow.Down → iterate fields top-to-bottom, stop on first error
├─ Flow.Up → iterate fields bottom-to-top, stop on first error
└─ Flow.Splash → validate all fields, collect all errors
│
▼
ValidationField.valid()
│
├─ Type.Required → blank/null check
├─ Type.Email → regex check
├─ Type.MustBeMoreThan / MustBeLessThan / MustBeInRange → numeric compare
├─ Type.MustBeEqualTo → equality check
├─ Type.Custom → caller-supplied predicate
└─ Type.Optional → always passes
│
▼
onError(message?) called on each field
│
▼
validator.valid (Compose state) updated
Composition local
Form(validator) { ... } calls CompositionLocalProvider(LocalFormValidator provides validator). Any composable nested inside can read the current validator without prop-drilling:
val validator = LocalFormValidator.current
Snackbar variant
Form(validator, snackBarProperties) { ... } shows a ValidationSnackBar automatically when validate() returns false. SnackBarProperties controls the message text, duration, and appearance.
Form(
validator = validator,
snackBarProperties = SnackBarProperties(visibleDuration = 4000)
) { ... }
validate() return value
validate() returns true only when every field that was checked passed. Use it to gate submission:
Button(onClick = {
if (validator.validate()) {
submitForm()
}
}) { Text("Submit") }
validator.valid state
validator.valid is a Compose State<Boolean> that reflects the outcome of the last validate() call. It recomposes any composable that reads it:
Button(enabled = validator.valid, onClick = { submit() }) {
Text("Submit")
}
validator.errorMessage
When Flow.Down or Flow.Up stops at the first failing field, validator.errorMessage holds that field's error string. Useful for displaying a top-level error summary:
if (!validator.valid) {
Text(validator.errorMessage ?: "Please fix the errors above", color = MaterialTheme.colors.error)
}