Built-in rules
Yakcov ships rules for the common cases so you rarely have to write your own. Every rule
implements ValueValidatorRule<V>; drop them into a validator's rules list. To write your
own, see custom rules.
Blank / null pass through
The string format rules treat blank input as valid (MinLength included), and the
value-membership/bounds generic rules (InList, Min, Max, InRange) treat
null as valid — so Required (or generic Required) is the single source of
presence-checking. Compose it alongside the rule whenever an empty field should be rejected:
listOf(Email) accepts an empty field, listOf(Required, Email) doesn't. The presence/state
checks (Required, ListNotEmpty, IsChecked) and the date-part rules
(DayValidation/MonthValidation/YearValidation, which need a value to parse) are the
exceptions — they reject blank/null.
String rules
com.chrisjenx.yakcov.strings — validate the text of a TextField
(rememberTextFieldValueValidator).
| Rule | Passes when |
|---|---|
Required |
the value is not blank |
Email |
the value is a valid email address |
Phone(region) |
a valid phone number for an ISO region — needs libphonenumber, see phone validation |
PhoneFormat |
the value looks like a phone number — lenient, no extra dependency |
Numeric |
the value parses as a whole number |
Decimal |
the value parses as a decimal |
MinValue(n) / MaxValue(n) |
the parsed numeric value is >= n / <= n |
MinLength(n) / MaxLength(n) |
the length is within bounds (MinLength can trim and exclude whitespace) |
HexColor |
a CSS hex color: #RGB, #RGBA, #RRGGBB or #RRGGBBAA (case-insensitive) |
OneOf(allowed) |
the value is one of a Set<String> — trims and ignores case by default (ignoreCase / trim flags) |
DayValidation / MonthValidation / YearValidation |
the day/month/year part forms a valid date (given a sibling LocalDate) |
PasswordMatches(other) |
the value equals another field's value |
Generic rules
com.chrisjenx.yakcov.generic — validate a typed value T directly, with
rememberGenericValueValidator(state, rules).
| Rule | Passes when |
|---|---|
Required<T>() |
the value is not null |
InList(allowed) |
the value is one of allowed (null passes) |
ListNotEmpty() |
the List value is non-empty |
IsChecked / IsNotChecked |
a Boolean? is / isn't true |
Min(n) / Max(n) / InRange(min, max) |
a numeric N? where N : Number, Comparable is in range — null and NaN pass |
The typed numeric bounds complement the string-based MinValue/MaxValue for fields whose
value is already numeric. Because they pass null through, the validator's value type must be
nullable (e.g. Int?):
@Composable
fun QuantityField() {
// Typed numeric bounds validate the value directly (no string parsing). null and NaN
// pass — pair with generic Required for presence. The value type must be nullable
// (Int?) so the bounds' null pass-through type-checks.
val quantity = rememberGenericValueValidator<Int?>(
state = 1,
rules = listOf(Min(1), Max(99)),
)
Text(if (quantity.isValid) "OK" else "Enter 1–99")
}
Conditional rules
onlyWhen (and the underlying Optional) wrap any rule — string or generic — so it runs
only while a State<Boolean> is true, and passes otherwise. Use it for
conditionally-required or optional-when-hidden fields instead of writing a bespoke variant:
// onlyWhen wraps any rule so it runs only while a State<Boolean> is true; otherwise the
// value passes. Collapses conditionally-required / optional-when-hidden fields into the
// existing rules instead of a bespoke rule per case.
fun taxIdRules(isBusiness: State<Boolean>): List<ValueValidatorRule<String>> =
listOf(Required.onlyWhen(isBusiness), MinLength(9).onlyWhen(isBusiness))
For "optional, but at least N chars if the user types something", gate only Required and
leave the length rule ungated — Required.onlyWhen(state) owns the empty case while the ungated
MinLength still enforces length once a value is typed (because blank always passes through to
Required):
// "Optional, but at least N chars if the user types something." Gate only Required by the
// state; leave MinLength ungated. Required.onlyWhen owns the empty case (blank passes when
// not required), while the ungated MinLength still rejects a too-short value once typed —
// because every string rule treats blank as valid and defers emptiness to Required.
fun nicknameRules(required: State<Boolean>): List<ValueValidatorRule<String>> =
listOf(Required.onlyWhen(required), MinLength(3))
Severities
Rules aren't limited to pass/fail. ValidationResult.outcome() is ranked
ERROR > WARNING > INFO > SUCCESS; only ERROR blocks validate()/submission, while
warnings and info still surface through supportingText.
See custom rules for grading your own.