Skip to content

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.