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.
- 🚀 Annotation-based Validation: Define validation rules directly on your data models.
- 🛠️ KSP Powered: Compile-time metadata generation means no reflection overhead.
- 🎨 Compose Ready: Built-in support for
mutableStateOfbased form tracking. - 🌍 Multiplatform: Consistent validation logic across all KMP targets.
- 🧩 Extensible: Easily create custom validators for complex business logic.
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 FormMetadataRegistry in your commonMain source set. This is because KSP (as used in this project) does not yet support generating code directly into commonMain (Or i can't figure a way out😭).
Create a file at src/commonMain/kotlin/formdoc/registry/FormMetadataRegistry.kt with 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 commonMain generation 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 = ""
)- Annotations: You mark your models with
@Validatable. - KSP Processing: During compilation, the
FormProcessorscans these annotations. - Metadata Generation: It generates a
*Metadataclass (e.g.,UserMetadata) that implementsFormMetadata. This class contains a map of properties to their respective validators. - Metadata Registry: This kotlin object holds all the form metadata that generated. You can get the metadata for your form model by
FormMetadataRegistry.get<*YourFormModel*>()(e.g.,FormMetadataRegistry.get<UserProfile>()). - State Management:
FormStateuses this metadata to perform validation whenevervalidateAll()orsubmit()is called, updating theFieldStatereactively.
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.