{"id":13428795,"url":"https://github.com/afollestad/vvalidator","last_synced_at":"2025-03-16T01:33:35.212Z","repository":{"id":45902769,"uuid":"165433713","full_name":"afollestad/vvalidator","owner":"afollestad","description":"🤖 An easy to use form validator for Kotlin \u0026 Android.","archived":true,"fork":false,"pushed_at":"2021-02-21T05:03:23.000Z","size":645,"stargazers_count":658,"open_issues_count":14,"forks_count":51,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-11-24T17:47:52.031Z","etag":null,"topics":["android","androidx","form","kotlin","validation"],"latest_commit_sha":null,"homepage":"https://af.codes","language":"Kotlin","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/afollestad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"afollestad","ko_fi":"afollestad"}},"created_at":"2019-01-12T20:38:35.000Z","updated_at":"2024-08-09T20:13:19.000Z","dependencies_parsed_at":"2022-09-26T21:31:38.800Z","dependency_job_id":null,"html_url":"https://github.com/afollestad/vvalidator","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fvvalidator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fvvalidator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fvvalidator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afollestad%2Fvvalidator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/afollestad","download_url":"https://codeload.github.com/afollestad/vvalidator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243814905,"owners_count":20352037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","androidx","form","kotlin","validation"],"created_at":"2024-07-31T01:01:05.345Z","updated_at":"2025-03-16T01:33:34.749Z","avatar_url":"https://github.com/afollestad.png","language":"Kotlin","funding_links":["https://github.com/sponsors/afollestad","https://ko-fi.com/afollestad"],"categories":["Libraries"],"sub_categories":[],"readme":"## VValidator (BETA)\n\n*View Validator*, an easy-to-use form validation library for Kotlin \u0026 Android.\n\n[ ![Maven Central](https://img.shields.io/maven-central/v/com.afollestad/vvalidator?style=flat\u0026label=Maven+Central) ](https://repo1.maven.org/maven2/com/afollestad/vvalidator)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/0d1f2118793443ecbf2df4d7af7d6fec)](https://www.codacy.com/app/drummeraidan_50/vvalidator?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=afollestad/vvalidator\u0026amp;utm_campaign=Badge_Grade)\n[![Android CI](https://github.com/afollestad/vvalidator/workflows/Android%20CI/badge.svg)](https://github.com/afollestad/vvalidator/actions?query=workflow%3A%22Android+CI%22)\n[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html)\n\n\u003cimg src=\"https://raw.githubusercontent.com/afollestad/vvalidator/master/images/showcase4.png\" width=\"600\" /\u003e\n\n---\n\n## Table of Contents\n\n1. [Gradle Dependency](#gradle-dependency)\n2. [The Basics](#the-basics)\n3. [Field Types](#field-types)\n    1. [Input](#input)\n    2. [Input Layout](#input-layout)\n    3. [Checkable](#checkable)\n    4. [Spinner](#spinner)\n    5. [Seeker](#seeker)\n4. [Assertion Descriptions](#assertion-descriptions)\n5. [Validation Results](#validation-results)\n6. [Error Handling](#error-handling)\n7. [Submit With](#submit-with)\n8. [Conditionals](#conditionals)\n9. [Supporting Additional Views](#supporting-additional-views)\n10. [Real Time Validation](#real-time-validation)\n\n---\n\n## Gradle Dependency\n\nAdd this to your module's `build.gradle` file:\n\n```gradle\ndependencies {\n  \n  implementation 'com.afollestad:vvalidator:0.5.2'\n}\n```\n\n---\n\n## The Basics\n\nVValidator works automatically within any Activity or AndroidX Fragment.\n\n```kotlin\nclass MyActivity : AppCompatActivity() {\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.my_layout)\n    \n    form {\n      input(R.id.your_edit_text) {\n        isNotEmpty()\n      }\n      submitWith(R.id.submit) { result -\u003e\n        // this block is only called if form is valid.\n        // do something with a valid form state.\n      }\n    }\n  }\n}\n```\n\nThe example above asserts that an edit text is not empty when a button a clicked. If that edit text \nis not empty when the button is clicked, the callback that the comment is in is invoked.\n\n---\n\n## Field Types\n\n### Input\n\nThe most basic type of supported view is an `EditText`.\n\n```kotlin\nform {\n  input(R.id.view_id, name = \"Optional Name\") {\n    isNotEmpty()\n    \n    isUri()\n    isUri().hasScheme(\"file\")\n    isUri().that { uri -\u003e uri.getQueryParameter(\"q\") != null }\n    \n    isUrl() // isUri() with defaults to require http/https and a hostname\n    isEmail()\n    \n    isNumber()\n    isNumber().lessThan(5)\n    isNumber().atMost(5)\n    isNumber().exactly(5)\n    isNumber().atLeast(5)\n    isNumber().greaterThan(5)\n    \n    isDecimal()\n    isDecimal().lessThan(5.2)\n    isDecimal().atMost(5.2)\n    isDecimal().exactly(5.2)\n    isDecimal().atLeast(5.2)\n    isDecimal().greaterThan(5.2)\n    \n    length().lessThan(5)\n    length().atMost(5)\n    length().exactly(5)\n    length().atLeast(5)\n    length().greaterThan(5)\n    \n    contains(\"Hello, World!\")\n    contains(\"Hello, World!\").ignoreCase()\n    \n    matches(\"/^(\\+?\\d{1,3}|\\d{1,4})$/\")\n    \n    // Custom assertions\n    assert(\"expected something\") { view -\u003e true }\n  }\n}\n```\n\n### Input Layout\n\nThis is basically identical to input. However, this targets\n`TextInputLayout` views from the Google Material library. Errors \nare shown differently by this view type and text is pulled \nfrom the child `TextInputEditText` rather than the parent.\n\n```kotlin\nform {\n  inputLayout(R.id.view_id, name = \"Optional Name\") {\n    isNotEmpty()\n    \n    isUri()\n    isUri().hasScheme(\"file\")\n    isUri().that { uri -\u003e uri.getQueryParameter(\"q\") != null }\n    \n    isUrl() // isUri() with defaults to require http/https and a hostname\n    isEmail()\n    \n    isNumber()\n    isNumber().lessThan(5)\n    isNumber().atMost(5)\n    isNumber().exactly(5)\n    isNumber().atLeast(5)\n    isNumber().greaterThan(5)\n    \n    isDecimal()\n    isDecimal().lessThan(5.2)\n    isDecimal().atMost(5.2)\n    isDecimal().exactly(5.2)\n    isDecimal().atLeast(5.2)\n    isDecimal().greaterThan(5.2)\n    \n    length().lessThan(5)\n    length().atMost(5)\n    length().exactly(5)\n    length().atLeast(5)\n    length().greaterThan(5)\n    \n    contains(\"Hello, World!\")\n    contains(\"Hello, World!\").ignoreCase()\n    \n    matches(\"/^(\\+?\\d{1,3}|\\d{1,4})$/\")\n    \n    // Custom assertions\n    assert(\"expected something\") { view -\u003e true }\n  }\n}\n```\n\n### Checkable\n\nMore specifically, a `CompoundButton`. This includes `Switch`,\n`RadioButton`, and `CheckBox` views.\n\n```kotlin\nform {\n  checkable(R.id.view_id, name = \"Optional Name\") {\n    isChecked()\n    isNotChecked()\n    // Custom assertions\n    assert(\"expected something\") { view -\u003e true }\n  }\n}\n```\n\n### Spinner\n\nA `Spinner` is Android's core drop down view. It attaches to an \nadapter, and shows a list of options when tapped.\n\n```kotlin\nform {\n  spinner(R.id.view_id, name = \"Optional Name\") {\n    selection().exactly(1)\n    selection().lessThan(1)\n    selection().atMost(1)\n    selection().atLeast(1)\n    selection().greaterThan(1)\n    // Custom assertions\n    assert(\"expected something\") { view -\u003e true }\n  }\n}\n```\n\n### Seeker\n\nAn `AbsSeekBar` includes Android's core `SeekBar` and `RatingBar` views. They allow you to select \na number either with a horizontally sliding view or with horizontal icons.\n\n```kotlin\nform {\n  seeker(R.id.view_id, name = \"Optional Name\") {\n    progress().exactly(1)\n    progress().lessThan(1)\n    progress().atMost(1)\n    progress().atLeast(1)\n    progress().greaterThan(1)\n    // Custom assertions\n    assert(\"expected something\") { view -\u003e true }\n  }\n}\n```\n\n---\n\n## Assertion Descriptions\n\nAll assertions expose a `description(String)` method, used to specify a custom message for \nassertion validation errors. They end up in the `description` field of `FieldError` instances.\n\nAll assertions provide default validation failure messages, however they may not be what you want \nto display to your app users.\n\n```kotlin\nform {\n  input(R.id.some_input) {\n    isNotEmpty().description(\"Please enter a value!\")\n  }\n  spinner(R.id.some_spinner) {\n    selection()\n      .greaterThan(0)\n      .description(\"Please make a selection!\")\n  }\n}\n```\n\n---\n\n## Validation Results\n\nYou get an instance of `FormResult` through the `submitWith(...)` callbacks. You can also get one\nwhen you manually validate your form.\n\n```kotlin\nval myForm = form {\n  ...\n}\n\nval result: FormResult = myForm.validate()\n```\n\nA call to `validate()` goes through all of your fields, making all of the set assertions, and propagating \nerrors through `onErrors` callbacks (which may or may not show errors in the UI automatically).\n\nThis result class gives you access to some detailed information.\n\n```kotlin\nval result: FormResult = // ...\n\nval isSuccess: Boolean = result.success()\nval hasErrors: Boolean = result.hasErrors()\n\nval errors: List\u003cFieldError\u003e = result.errors()\n\nval values: List\u003cFieldValue\u003c*\u003e\u003e = result.values()\nval singleValue: FieldValue\u003c*\u003e = result[\"Field Name\"]\n\nsingleValue.asString()\nsingleValue.asInt()\nsingleValue.asLong()\nsingleValue.asFloat()\nsingleValue.asDouble()\nsingleValue.asBoolean()\n```\n\nEach instance of `FieldError` contains additional information:\n\n```kotlin\nval error: FieldError = // ...\n\n// view ID\nval id: Int = error.id      \n// field/view name\nval name: String = error.name\n// assertion description - what the failure is\nval description: String = error.description\n// the class of the assertion that failed\nval assertionType: KClass\u003cout Assertion\u003c*, *\u003e\u003e = error.assertionType\n```\n\n---\n\n## Error Handling\n\nInput and Input Layout fields have default error handling because their underlying views have an \nerror property provided by Android. However, other view types do not. This library provides an \nerror hook for each field that you can use to display errors in the UI.\n\n```kotlin\nform {\n  checkable(R.id.view_id, name = \"Optional Name\") {\n    isChecked() \n    onErrors { view, errors -\u003e\n      // `view` here is a CompoundButton.\n      // `errors` here is a List\u003cFieldError\u003e, which can be empty to notify that there are no longer \n      // any validation errors.\n      val firstError: FieldError? = errors.firstOrNull()\n      // Show firstError.toString() in the UI.\n    }\n  }\n}\n```\n\n---\n\n## Submit With\n\nYou can have this library automatically handle validating your form with the click of a `Button`:\n\n```kotlin\nform {\n  submitWith(R.id.button_id) { result -\u003e\n    // Button was clicked and form is completely valid!\n  }\n}\n```\n\nOr even a `MenuItem`:\n\n```kotlin\nval menu: Menu = // ...\n\nform {\n  submitWith(menu, R.id.item_id) { result -\u003e\n    // Item was clicked and form is completely valid!\n  }\n}\n```\n\n---\n\n## Conditionals\n\nYou can apply assertions conditionally. **Anything outside of a `conditional` block is still always \nexecuted during validation.** This could be useful in many cases. One use case would be on fields that are optionally \nvisible. *If a field is not visible, it should not be validated.* \n\n```kotlin\nform {\n  input(R.id.input_site, name = \"Site\") {\n    conditional({ spinner.selectedItemPosition \u003e 1 }) {\n      isUrl()\n    }\n  }\n}\n```\n\nThe `conditional(..)` block above only asserts the field is a URL if a spinner's selection is greater \nthan 1. Say the spinner makes the `input_site` field visible if its selection is \u003e 1.\n\nYou can nest conditions as well:\n\n```kotlin\nform {\n  input(...) {\n    conditional(...) {\n      isNotEmpty()\n      conditional(...) {\n        length().greaterThan(0)\n      }\n      conditional(...) {\n        isNumber()\n      }\n    }\n  }\n}\n```\n\nYou may have noticed somewhere above that `input(...)` and `inputLayout(...)` have an optional \nargument named `optional`. Internally, this uses `conditional` to only apply inner assertions if\nthe input field's text is not empty.\n\n---\n\n## Supporting Additional Views\n\nIf you need to support a view type that isn't supported out of the box, you can create custom \nassertions and form fields.\n\nFirst, you'd need an assertion class that goes with your view.\n\n```kotlin\nclass MyView(context: Context) : EditText(context, null)\n\nclass MyAssertion : Assertion\u003cMyView, MyAssertion\u003e() {\n  override fun isValid(view: MyView): Boolean {\n    return view.text.isNotEmpty()\n  }\n\n  override fun defaultDescription(): String {\n    return \"edit text should not be empty\"\n  }\n}\n```\n\nThen you'll need a custom `FormField` class:\n\n```kotlin\nclass MyField(\n  container: ValidationContainer,\n  view: MyView,\n  name: String\n) : FormField\u003cMyField, MyView, CharSequence\u003e(container, id, name) {\n  init {\n    onErrors { myView, errors -\u003e\n      // Do some sort of default error handling with views\n    }\n  }\n  \n  // Your first custom assertion\n  fun myAssertion() = assert(MyAssertion())\n  \n  override fun obtainValue(\n    id: Int,\n    name: String\n  ): FieldValue\u003cCharSequence\u003e? {\n    val currentValue = view.text as? CharSequence ?: return null\n    return TextFieldValue(\n        id = id,\n        name = name,\n        value = currentValue\n    )\n  }\n  \n  override fun startRealTimeValidation(debounce: Int) {\n    // See the \"Real Time Validation\" section below.\n    // You'd want to begin observing input to the view this field attaches to,\n    // and call `validate()` on this field when it changes. You should respect the \n    // `debounce` parameter as well.\n  }\n}\n```\n\nFinally, you can add an extension to `Form`:\n\n```kotlin\nfun Form.myView(\n  view: MyView,\n  name: String? = null,\n  builder: FieldBuilder\u003cMyField\u003e\n) {\n  val newField = MyField(\n      container = container.checkAttached(),\n      view = view,\n      name = name\n  )\n  builder(newField)\n  appendField(newField)\n}\n\nfun Form.myView(\n  @IdRes id: Int,\n  name: String? = null,\n  builder: FieldBuilder\u003cMyField\u003e\n) = myView(\n  view = container.getViewOrThrow(id),\n  name = name,\n  builder = builder\n)\n```\n\nNow, you can use it:\n\n```kotlin\nform {\n  myView(R.id.seek_bar, name = \"Optional Name\") {\n    myAssertion()\n  }\n}\n```\n\nWhen the form is validated, your assertion's `isValid(MyView)` method is executed. If it returns \nfalse, this view is marked as erroneous in the validation results.\n\n---\n\n## Real Time Validation\n\nThis library provides an option to support real time validation. Rather than performing validation \nwhen you call `validate()` on the `Form`, or `validate()` on an individual field, the library \nmakes these calls for you as the view's change. \n\n```kotlin\nform {\n  useRealTimeValidation()\n  input(R.id.your_edit_text) {\n    isNotEmpty()\n  }\n}\n```\n\nWith this example above, the form will automatically perform validation when the input field's \ntext changes. *Note that this does work with all field types, not just input fields.*\n\n`useRealTimeValidation()` has an optional `Int` parameter that lets you set a custom debounce delay. \nThis delay is how much of a gap there is between a field's value changing and validation being \nperformed. This prevents too many validations from occurring in a row, such as a user is typing in \nan input field - you wouldn't want to validate with every single letter input. *The default value \nis 500 (milliseconds), or a half second.*\n\n---\n\nAnother optional parameter on `useRealTimeValidation() is `disableSubmit`. When true,\nthe `submitWith` view or item you set will be enabled or disabled based on the real time \nvalid state of the overall form.\n\n```kotlin\nform {\n  useRealTimeValidation(disableSubmit = true)\n  input(R.id.your_edit_text) {\n    isNotEmpty()\n  }\n  submitWith(R.id.my_button) {\n    // Do something\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafollestad%2Fvvalidator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fafollestad%2Fvvalidator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafollestad%2Fvvalidator/lists"}