Skip to content

DeFerence3/form-doctor

Repository files navigation

FormDoc 📝

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.


✨ Features

  • 🚀 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 mutableStateOf based form tracking.
  • 🌍 Multiplatform: Consistent validation logic across all KMP targets.
  • 🧩 Extensible: Easily create custom validators for complex business logic.

🛠️ Usage

1. Annotate your Data Model

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
)

2. Compose Integration

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")
            }
        }
    }
}

3. Custom Validators

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 = ""
)

🏗️ How it Works

  1. Annotations: You mark your models with @Validatable.
  2. KSP Processing: During compilation, the FormProcessor scans these annotations.
  3. Metadata Generation: It generates a *Metadata class (e.g., UserMetadata) that implements FormMetadata. This class contains a map of properties to their respective validators.
  4. 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>()).
  5. State Management: FormState uses this metadata to perform validation whenever validateAll() or submit() is called, updating the FieldState reactively.

📦 Installation

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")
}

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


Built with ❤️ for the Kotlin Multiplatform community.

About

A Compose Multiplatform form validating library

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages