
Annotation-based form validation with KSP-powered compile-time metadata, zero-reflection overhead, reactive Compose-ready mutable-state form tracking, field-level error reporting, and extensible custom validators.
FormDoc is a powerful Kotlin Multiplatform library designed to simplify form validation. By leveraging KSP (Kotlin Symbol Processing) and Annotations, it automates the generation of validation metadata, allowing you to focus on building great UIs.
Designed with Compose Multiplatform in mind, FormDoc provides a seamless way to manage form states and validation errors across Android, iOS, Desktop, and Web.
mutableStateOf based form tracking.Mark your class with @Validatable and use built-in constraints like @NotBlank, @Email, or @Min.
@Validatable
data class UserProfile(
@NotBlank(message = "Username is required")
val username: String = "",
@Email(message = "Invalid email format")
val email: String = "",
@Min(value = 18, message = "Must be at least 18 years old")
val age: Int = 0
)[!IMPORTANT] Currently, you need to manually create the
FormMetadataRegistryin yourcommonMainsource set. This is because KSP (as used in this project) does not yet support generating code directly intocommonMain(Or i can't figure a way out😭).Create a file at
src/commonMain/kotlin/formdoc/registry/FormMetadataRegistry.ktwith the following content:package formdoc.registry import me.deference.formdoc.FormMetadata expect object FormMetadataRegistry { inline fun <reified T : Any> get(): FormMetadata<T>? }Once KSP support (Or my skill issue is solved🥲) for
commonMaingeneration is improved, this manual step will be removed.
Use rememberFormState to initialize your form. FormDoc uses the KSP-generated UserProfileMetadata to apply the rules, which can be obtained from FormMetadataRegistry.get<UserProfileMetadata>().
@Composable
fun RegistrationForm() {
val formState = rememberFormState(
initial = UserProfile(),
metadata = FormMetadataRegistry.get<UserProfile>() // Generated by KSP
)
FormContent(formState) {
Column {
val nameState = state.getState(UserProfile::username)
TextField(
value = nameState.value,
onValueChange = { nameState.value = it },
label = { Text("Username") },
isError = nameState.error != null,
supportingText = { nameState.error?.let { Text(it) } }
)
Button(onClick = {
state.submit(
onValid = { data -> println("Success: $data") },
onInvalid = { errors -> println("Errors: $errors") }
)
}) {
Text("Submit")
}
}
}
}Need something specific? Implement FieldValidator and use @ValidatedBy.
class PasswordValidator : FieldValidator<String> {
override fun validate(value: String): String? {
return if (value.length < 8) "Password too short" else null
}
}
@Validatable
data class Login(
@ValidatedBy(PasswordValidator::class)
val password: String = ""
)@Validatable.FormProcessor scans these annotations.*Metadata class (e.g., UserMetadata) that implements FormMetadata. This class contains a map of properties to their respective validators.FormMetadataRegistry.get<*YourFormModel*>() (e.g., FormMetadataRegistry.get<UserProfile>()).FormState uses this metadata to perform validation whenever validateAll() or submit() is called, updating the FieldState reactively.Add the KSP plugin and dependencies to your build.gradle.kts:
plugins {
id("com.google.devtools.ksp") version "x.x.x"
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.deference3:formdoc:0.0.2")
// other configurations
}
// other configurations
}
// other configurations
}
dependencies {
ksp("io.github:formdoc-processor:0.0.2")
}This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the Kotlin Multiplatform community.
FormDoc is a powerful Kotlin Multiplatform library designed to simplify form validation. By leveraging KSP (Kotlin Symbol Processing) and Annotations, it automates the generation of validation metadata, allowing you to focus on building great UIs.
Designed with Compose Multiplatform in mind, FormDoc provides a seamless way to manage form states and validation errors across Android, iOS, Desktop, and Web.
mutableStateOf based form tracking.Mark your class with @Validatable and use built-in constraints like @NotBlank, @Email, or @Min.
@Validatable
data class UserProfile(
@NotBlank(message = "Username is required")
val username: String = "",
@Email(message = "Invalid email format")
val email: String = "",
@Min(value = 18, message = "Must be at least 18 years old")
val age: Int = 0
)[!IMPORTANT] Currently, you need to manually create the
FormMetadataRegistryin yourcommonMainsource set. This is because KSP (as used in this project) does not yet support generating code directly intocommonMain(Or i can't figure a way out😭).Create a file at
src/commonMain/kotlin/formdoc/registry/FormMetadataRegistry.ktwith the following content:package formdoc.registry import me.deference.formdoc.FormMetadata expect object FormMetadataRegistry { inline fun <reified T : Any> get(): FormMetadata<T>? }Once KSP support (Or my skill issue is solved🥲) for
commonMaingeneration is improved, this manual step will be removed.
Use rememberFormState to initialize your form. FormDoc uses the KSP-generated UserProfileMetadata to apply the rules, which can be obtained from FormMetadataRegistry.get<UserProfileMetadata>().
@Composable
fun RegistrationForm() {
val formState = rememberFormState(
initial = UserProfile(),
metadata = FormMetadataRegistry.get<UserProfile>() // Generated by KSP
)
FormContent(formState) {
Column {
val nameState = state.getState(UserProfile::username)
TextField(
value = nameState.value,
onValueChange = { nameState.value = it },
label = { Text("Username") },
isError = nameState.error != null,
supportingText = { nameState.error?.let { Text(it) } }
)
Button(onClick = {
state.submit(
onValid = { data -> println("Success: $data") },
onInvalid = { errors -> println("Errors: $errors") }
)
}) {
Text("Submit")
}
}
}
}Need something specific? Implement FieldValidator and use @ValidatedBy.
class PasswordValidator : FieldValidator<String> {
override fun validate(value: String): String? {
return if (value.length < 8) "Password too short" else null
}
}
@Validatable
data class Login(
@ValidatedBy(PasswordValidator::class)
val password: String = ""
)@Validatable.FormProcessor scans these annotations.*Metadata class (e.g., UserMetadata) that implements FormMetadata. This class contains a map of properties to their respective validators.FormMetadataRegistry.get<*YourFormModel*>() (e.g., FormMetadataRegistry.get<UserProfile>()).FormState uses this metadata to perform validation whenever validateAll() or submit() is called, updating the FieldState reactively.Add the KSP plugin and dependencies to your build.gradle.kts:
plugins {
id("com.google.devtools.ksp") version "x.x.x"
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.deference3:formdoc:0.0.2")
// other configurations
}
// other configurations
}
// other configurations
}
dependencies {
ksp("io.github:formdoc-processor:0.0.2")
}This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the Kotlin Multiplatform community.