Code Example

Kotlin Android Form Validation Snippets

Step by step Android Form Validation examples.

1. Dhaval2404/android-form-validation

UI form validation library for Android ✔.

android-form-validation Example Tutorial
android-form-validation Example Tutorial

Yet another UI form validation library for Android. It is highly customizable and easy to use. This library will works with TextView, EditText, AppCompatEditText, TextInputEditText, TextInputLayout and CheckBox. This library is designed in such a way that Its easy to add support for new widgets and add new rules.

💻Usage

Gradle dependency:

implementation 'com.github.dhaval2404:android-form-validation:1.0'

The android-form-validation configuration is created using the builder pattern.

private fun isValidForm(): Boolean {
    return FormValidator.getInstance()
        .addField(firstNameEt, NonEmptyRule(R.string.error_empty_first_name))
        .addField(lastNameEt, NonEmptyRule(R.string.error_empty_last_name))
        .addField(
            emailEtLyt,
            NonEmptyRule("Please enter Email"),
            EmailRule(R.string.error_invalid_email)
        )
        .addField(
            passwordEtLyt,
            NonEmptyRule("Please enter Password"),
            PasswordRule("Please provide strong Password")
        )
        .addField(
            confirmPasswordEtLyt,
            NonEmptyRule("Please enter Password"),
            EqualRule(
                confirmPasswordEt.text.toString(),
                "Password and Confirm password must match"
            )
        )
        .addField(
            phoneNumberEt,
            NonEmptyRule("Please enter Phone Number"),
            LengthRule(10, "Please enter valid Phone Number")
        )
        .addField(
            termsOfUseCB,
            CheckedRule("Accept terms of use")
        )
        .setErrorListener {
            // Require only for CheckBox with Toast or Custom View Only
            for (error in it) {
                if (error.view is CheckBox) {
                    (error.view as CheckBox).error = null
                    Toast.makeText(applicationContext, error.error, Toast.LENGTH_SHORT).show()
                }
                Log.e("Error", error.toString())
            }
        }
        .validate()
}

📐Supported Rules:

CheckedRule(Used with CheckBox only)

addField(termsOfUseCheckbox, CheckedRule("Accept terms of use"))

EmailRule

addField(emailEditText, EmailRule("Please enter valid Email"))

EqualRule

// Combile 2 rules for Confirm Password Validation
// Check if Password is valid and Password match with Confirm Password
addField(confirmPasswordEditText,
    PasswordRule(PasswordPattern.ALPHA_NUMERIC, "Please provide strong Password"),
    EqualRule(passwordEditText.text.toString(), "Password not matching")
)
// Combile 2 rules for Confirm Email Validation
// Check if Email is valid and Email match with Confirm Email
addField(emailEditText,
    EmailRule("Please enter valid Email"),
    EqualRule(confirmEmailEditText.text.toString(), "Email not matching")
)

LengthRule

addField(phoneNumberEditText, LengthRule(10, "Please enter valid Phone Number"))

MaxLengthRule

addField(messageEditText, MaxLengthRule(50, "Message should be less than 50 character long"))

MinLengthRule

addField(messageEditText, MinLengthRule(10, "Please enter message with at least 10 character"))
// Check if Phone Number Length is between 10-13
addField(phoneNumberEditText,
    MinLengthRule(10, "Please enter valid Phone Number"),
    MaxLengthRule(13, "Please enter valid Phone Number"))

NumberRule

addField(priceEditText, NumberRule("Please enter valid Price"))

NonEmptyRule

addField(firstNameEditText, NonEmptyRule("Please enter First Name"))

PasswordRule

// Password can have any characters
addField(passwordEditText, PasswordRule("Please enter Password"))
// Password should have alphabets and numeric character both
addField(passwordEditText, PasswordRule(PasswordPattern.ALPHA_NUMERIC, "Please provide strong Password"))

RegexRule

addField(usernameEditText, RegexRule(RegexRule.USERNAME_PATTERN, "Please enter valid Username"))
addField(usernameEditText, RegexRule("^[a-zA-Z0-9_-]{3,16}",  "Please enter valid Username"))

Full Example

Let us look at a full Example below.

Step 1. Design Layouts

We need to design our XML layouts as follows:

(a). content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        app:cardCornerRadius="8dp"
        app:cardElevation="10dp"
        app:contentPadding="16dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <EditText
                    android:id="@+id/firstNameEt"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:inputType="textPersonName"
                    android:hint="@string/hint_first_name" />

                <EditText
                    android:id="@+id/lastNameEt"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="10dp"
                    android:layout_weight="1"
                    android:inputType="textPersonName"
                    android:hint="@string/hint_last_name" />

            </LinearLayout>

            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/phoneNumberEt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:hint="@string/hint_phone_number"
                android:inputType="phone"
                android:maxLines="10" />

            <!-- Set app:errorIconDrawable="@null" to Hide Error Icon-->
            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/emailEtLyt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                app:errorIconDrawable="@null">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/emailEt"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/hint_email_address"
                    android:inputType="textEmailAddress" />

            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/passwordEtLyt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                app:passwordToggleEnabled="true">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/passwordEt"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/hint_password"
                    android:inputType="textPassword" />

            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/confirmPasswordEtLyt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                app:passwordToggleEnabled="true">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/confirmPasswordEt"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/hint_confirm_password"
                    android:inputType="textPassword" />

            </com.google.android.material.textfield.TextInputLayout>

            <androidx.appcompat.widget.AppCompatCheckBox
                android:id="@+id/termsOfUseCB"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/message_terms_of_use" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/btnSubmit"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="12dp"
                android:layout_marginBottom="10dp"
                android:onClick="onSubmitClick"
                android:padding="16dp"
                android:hint="@string/action_register"
                app:cornerRadius="6dp" />

        </LinearLayout>

    </com.google.android.material.card.MaterialCardView>

</LinearLayout>

(b). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Step 2. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

package com.github.dhaval2404.form_validation.sample

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.CheckBox
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.github.dhaval2404.form_validation.constant.PasswordPattern
import com.github.dhaval2404.form_validation.rule.CheckedRule
import com.github.dhaval2404.form_validation.rule.EmailRule
import com.github.dhaval2404.form_validation.rule.EqualRule
import com.github.dhaval2404.form_validation.rule.LengthRule
import com.github.dhaval2404.form_validation.rule.NonEmptyRule
import com.github.dhaval2404.form_validation.rule.PasswordRule
import com.github.dhaval2404.form_validation.validation.FormValidator
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*

/**
 * Library Demo
 *
 * @author Dhaval Patel
 * @version 1.0
 * @since 28 March 2020
 */
class MainActivity : AppCompatActivity() {

    companion object {

        private const val PHONE_NUMBER_LENGTH = 10
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }

    fun onSubmitClick(view: View) {
        if (isValidForm()) {
            Toast.makeText(view.context, "Submit Form", Toast.LENGTH_SHORT).show()
        }
    }

    private fun isValidForm(): Boolean {
        return FormValidator.getInstance()
            .addField(firstNameEt, NonEmptyRule(R.string.error_empty_first_name))
            .addField(lastNameEt, NonEmptyRule(R.string.error_empty_last_name))
            .addField(
                emailEtLyt,
                NonEmptyRule("Please enter Email"),
                EmailRule(R.string.error_invalid_email)
            )
            .addField(
                passwordEtLyt,
                NonEmptyRule("Please enter Password"),
                PasswordRule(PasswordPattern.ALPHA_NUMERIC, "Please provide strong Password")
            )
            .addField(
                confirmPasswordEtLyt,
                NonEmptyRule("Please enter Password"),
                EqualRule(
                    confirmPasswordEt.text.toString(),
                    "Password and Confirm password must match"
                )
            )
            .addField(
                phoneNumberEt,
                NonEmptyRule("Please enter Phone Number"),
                LengthRule(PHONE_NUMBER_LENGTH, "Please enter valid Phone Number")
            )
            .addField(
                termsOfUseCB,
                CheckedRule("Accept terms of use")
            )
            .setErrorListener {
                for (error in it) {
                    if (error.view is CheckBox) {
                        (error.view as CheckBox).error = null
                        Toast.makeText(applicationContext, error.error, Toast.LENGTH_SHORT).show()
                    }
                    Log.e("Error", error.toString())
                }
            }
            .validate()
    }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

2. afollestad/vvalidator

🤖 An easy to use form validator for Kotlin & Android..

View Validator, an easy-to-use form validation library for Kotlin & Android.

vvalidator Example Tutorial
vvalidator Example Tutorial
vvalidator Example Tutorial

Gradle Dependency

Add this to your module's build.gradle file:

dependencies {

  implementation 'com.afollestad:vvalidator:0.5.2'
}

The Basics

VValidator works automatically within any Activity or AndroidX Fragment.

class MyActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.my_layout)

    form {
      input(R.id.your_edit_text) {
        isNotEmpty()
      }
      submitWith(R.id.submit) { result ->
        // this block is only called if form is valid.
        // do something with a valid form state.
      }
    }
  }
}

Field Types

Input

The most basic type of supported view is an EditText.

form {
  input(R.id.view_id, name = "Optional Name") {
    isNotEmpty()

    isUri()
    isUri().hasScheme("file")
    isUri().that { uri -> uri.getQueryParameter("q") != null }

    isUrl() // isUri() with defaults to require http/https and a hostname
    isEmail()

    isNumber()
    isNumber().lessThan(5)
    isNumber().atMost(5)
    isNumber().exactly(5)
    isNumber().atLeast(5)
    isNumber().greaterThan(5)

    isDecimal()
    isDecimal().lessThan(5.2)
    isDecimal().atMost(5.2)
    isDecimal().exactly(5.2)
    isDecimal().atLeast(5.2)
    isDecimal().greaterThan(5.2)

    length().lessThan(5)
    length().atMost(5)
    length().exactly(5)
    length().atLeast(5)
    length().greaterThan(5)

    contains("Hello, World!")
    contains("Hello, World!").ignoreCase()

    matches("/^(+?d{1,3}|d{1,4})$/")

    // Custom assertions
    assert("expected something") { view -> true }
  }
}

Input Layout

form {
  inputLayout(R.id.view_id, name = "Optional Name") {
    isNotEmpty()

    isUri()
    isUri().hasScheme("file")
    isUri().that { uri -> uri.getQueryParameter("q") != null }

    isUrl() // isUri() with defaults to require http/https and a hostname
    isEmail()

    isNumber()
    isNumber().lessThan(5)
    isNumber().atMost(5)
    isNumber().exactly(5)
    isNumber().atLeast(5)
    isNumber().greaterThan(5)

    isDecimal()
    isDecimal().lessThan(5.2)
    isDecimal().atMost(5.2)
    isDecimal().exactly(5.2)
    isDecimal().atLeast(5.2)
    isDecimal().greaterThan(5.2)

    length().lessThan(5)
    length().atMost(5)
    length().exactly(5)
    length().atLeast(5)
    length().greaterThan(5)

    contains("Hello, World!")
    contains("Hello, World!").ignoreCase()

    matches("/^(+?d{1,3}|d{1,4})$/")

    // Custom assertions
    assert("expected something") { view -> true }
  }
}

Checkable

form {
  checkable(R.id.view_id, name = "Optional Name") {
    isChecked()
    isNotChecked()
    // Custom assertions
    assert("expected something") { view -> true }
  }
}

Spinner

Spinner

form {
  spinner(R.id.view_id, name = "Optional Name") {
    selection().exactly(1)
    selection().lessThan(1)
    selection().atMost(1)
    selection().atLeast(1)
    selection().greaterThan(1)
    // Custom assertions
    assert("expected something") { view -> true }
  }
}

Seeker

form {
  seeker(R.id.view_id, name = "Optional Name") {
    progress().exactly(1)
    progress().lessThan(1)
    progress().atMost(1)
    progress().atLeast(1)
    progress().greaterThan(1)
    // Custom assertions
    assert("expected something") { view -> true }
  }
}

Assertion Descriptions

form {
  input(R.id.some_input) {
    isNotEmpty().description("Please enter a value!")
  }
  spinner(R.id.some_spinner) {
    selection()
      .greaterThan(0)
      .description("Please make a selection!")
  }
}

Validation Results

val myForm = form {
  ...
}

val result: FormResult = myForm.validate()

This result class gives you access to some detailed information.

val result: FormResult = // ...

val isSuccess: Boolean = result.success()
val hasErrors: Boolean = result.hasErrors()

val errors: List<FieldError> = result.errors()

val values: List<FieldValue<*>> = result.values()
val singleValue: FieldValue<*> = result["Field Name"]

singleValue.asString()
singleValue.asInt()
singleValue.asLong()
singleValue.asFloat()
singleValue.asDouble()
singleValue.asBoolean()

Each instance of FieldError contains additional information:

val error: FieldError = // ...

// view ID
val id: Int = error.id      
// field/view name
val name: String = error.name
// assertion description - what the failure is
val description: String = error.description
// the class of the assertion that failed
val assertionType: KClass<out Assertion<*, *>> = error.assertionType

Error Handling

form {
  checkable(R.id.view_id, name = "Optional Name") {
    isChecked() 
    onErrors { view, errors ->
      // view here is a CompoundButton.
      // errors here is a List<FieldError>, which can be empty to notify that there are no longer 
      // any validation errors.
      val firstError: FieldError? = errors.firstOrNull()
      // Show firstError.toString() in the UI.
    }
  }
}

Submit With

You can have this library automatically handle validating your form with the click of a Button:

form {
  submitWith(R.id.button_id) { result ->
    // Button was clicked and form is completely valid!
  }
}

Or even a MenuItem:

val menu: Menu = // ...

form {
  submitWith(menu, R.id.item_id) { result ->
    // Item was clicked and form is completely valid!
  }
}

Conditionals

conditional

form {
  input(R.id.input_site, name = "Site") {
    conditional({ spinner.selectedItemPosition > 1 }) {
      isUrl()
    }
  }
}

You can nest conditions as well:

form {
  input(...) {
    conditional(...) {
      isNotEmpty()
      conditional(...) {
        length().greaterThan(0)
      }
      conditional(...) {
        isNumber()
      }
    }
  }
}

Supporting Additional Views

First, you'd need an assertion class that goes with your view.

class MyView(context: Context) : EditText(context, null)

class MyAssertion : Assertion<MyView, MyAssertion>() {
  override fun isValid(view: MyView): Boolean {
    return view.text.isNotEmpty()
  }

  override fun defaultDescription(): String {
    return "edit text should not be empty"
  }
}

Then you'll need a custom FormField class:

class MyField(
  container: ValidationContainer,
  view: MyView,
  name: String
) : FormField<MyField, MyView, CharSequence>(container, id, name) {
  init {
    onErrors { myView, errors ->
      // Do some sort of default error handling with views
    }
  }

  // Your first custom assertion
  fun myAssertion() = assert(MyAssertion())

  override fun obtainValue(
    id: Int,
    name: String
  ): FieldValue<CharSequence>? {
    val currentValue = view.text as? CharSequence ?: return null
    return TextFieldValue(
        id = id,
        name = name,
        value = currentValue
    )
  }

  override fun startRealTimeValidation(debounce: Int) {
    // See the "Real Time Validation" section below.
    // You'd want to begin observing input to the view this field attaches to,
    // and call validate() on this field when it changes. You should respect the 
    // debounce parameter as well.
  }
}

Finally, you can add an extension to Form:

fun Form.myView(
  view: MyView,
  name: String? = null,
  builder: FieldBuilder<MyField>
) {
  val newField = MyField(
      container = container.checkAttached(),
      view = view,
      name = name
  )
  builder(newField)
  appendField(newField)
}

fun Form.myView(
  @IdRes id: Int,
  name: String? = null,
  builder: FieldBuilder<MyField>
) = myView(
  view = container.getViewOrThrow(id),
  name = name,
  builder = builder
)

Now, you can use it:

form {
  myView(R.id.seek_bar, name = "Optional Name") {
    myAssertion()
  }
}

Real Time Validation

form {
  useRealTimeValidation()
  input(R.id.your_edit_text) {
    isNotEmpty()
  }
}
form {
  useRealTimeValidation(disableSubmit = true)
  input(R.id.your_edit_text) {
    isNotEmpty()
  }
  submitWith(R.id.my_button) {
    // Do something
  }
}

Full Example

Let us look at a full android sample project.

Step 1. Design Layouts

We need to design our XML layouts as follows:

(a). list_item_spinner_dropdown.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="52dp"
    android:gravity="center_vertical|start"
    android:paddingLeft="@dimen/content_inset"
    android:paddingRight="@dimen/content_inset"
    style="@style/TextAppearance.MaterialComponents.Body1"
    />

(b). list_item_spinner.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:gravity="center_vertical|start"
    style="@style/TextAppearance.MaterialComponents.Body1"
    />

(c). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    >

  <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:paddingBottom="@dimen/content_inset_double"
      android:paddingLeft="@dimen/content_inset"
      android:paddingRight="@dimen/content_inset"
      android:paddingTop="@dimen/content_inset_less"
      tools:ignore="HardcodedText,Autofill"
      >

    <!-- Name -->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_name"
        android:hint="Your Name"
        style="@style/Form.InputLayout"
        >

      <com.google.android.material.textfield.TextInputEditText
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:inputType="text|textPersonName"
          />

    </com.google.android.material.textfield.TextInputLayout>

    <!-- Email -->
    <TextView
        android:text="Email"
        style="@style/Form.Label"
        />

    <EditText
        android:id="@+id/input_email"
        android:hint="Your email address"
        android:inputType="text|textEmailAddress"
        style="@style/Form.InputField"
        />

    <!-- Age -->
    <TextView
        android:text="Age"
        style="@style/Form.Label"
        />

    <EditText
        android:id="@+id/input_age"
        android:hint="Your age"
        android:inputType="number"
        style="@style/Form.InputField"
        />

    <!-- Weight -->
    <TextView
        android:text="Weight"
        style="@style/Form.Label"
        />

    <EditText
        android:id="@+id/input_weight"
        android:hint="Your weight"
        android:inputType="numberDecimal"
        style="@style/Form.InputField"
        />

    <!-- Spinner -->
    <Spinner
        android:id="@+id/input_spinner"
        style="@style/Form.Dropdown"
        />

    <TextView
        android:id="@+id/error_spinner"
        android:visibility="gone"
        style="@style/Form.Error"
        />

    <!-- Website -->
    <TextView
        android:id="@+id/label_site"
        android:text="Website"
        style="@style/Form.Label"
        />

    <EditText
        android:id="@+id/input_site"
        android:hint="Your site's URL"
        android:inputType="text|textUri"
        style="@style/Form.InputField"
        />

    <!-- Biography -->
    <TextView
        android:text="Biography"
        style="@style/Form.Label"
        />

    <EditText
        android:id="@+id/input_bio"
        android:gravity="top|start"
        android:hint="A short bio"
        android:inputType="text|textAutoCorrect|textCapSentences"
        android:minLines="3"
        style="@style/Form.InputField"
        />

    <SeekBar
        android:id="@+id/seek_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="-8dp"
        android:layout_marginRight="-8dp"
        android:layout_marginTop="@dimen/content_inset"
        android:max="100"
        android:progress="25"
        />

    <TextView
        android:id="@+id/error_seekBar"
        android:visibility="gone"
        style="@style/Form.Error"
        />

    <!-- Agree -->
    <CheckBox
        android:id="@+id/check_terms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="-4dp"
        android:layout_marginRight="-4dp"
        android:layout_marginTop="@dimen/content_inset"
        android:text="I agree to the terms &amp; conditions"
        />

    <TextView
        android:id="@+id/error_checkBox"
        android:visibility="gone"
        style="@style/Form.Error"
        />

    <!-- Submit -->
    <Button
        android:id="@+id/submit"
        android:text="Submit"
        style="@style/Form.Button"
        />

  </LinearLayout>

</ScrollView>

Step 2. Write Code

Finally we need to write our code as follows:

(a). Util.kt

/**
 * Designed and developed by Aidan Follestad (@afollestad)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.afollestad.vvalidatorsample

import android.app.Activity
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT

private var toast: Toast? = null

/** Shows a toast in the receiving Activity, cancelling any currently visible one. */
fun Activity.toast(message: String) {
  toast?.cancel()
  toast = Toast.makeText(this, message, LENGTH_SHORT)
      .apply {
        show()
      }
}

(b). MainActivity.kt

/**
 * Designed and developed by Aidan Follestad (@afollestad)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.afollestad.vvalidatorsample

import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.afollestad.vvalidator.form
import com.afollestad.vvalidator.util.onItemSelected
import com.afollestad.vvalidator.util.showOrHide

/** @author Aidan Follestad (@afollestad) */
class MainActivity : AppCompatActivity() {
  private val checkBoxError by lazy { findViewById<CheckBox>(R.id.error_checkBox) }
  private val seekBarError by lazy { findViewById<TextView>(R.id.error_seekBar) }
  private val spinnerError by lazy { findViewById<TextView>(R.id.error_spinner) }
  private val inputSite by lazy { findViewById<EditText>(R.id.input_site) }
  private val spinner by lazy { findViewById<Spinner>(R.id.input_spinner) }
  private val labelSite by lazy { findViewById<TextView>(R.id.label_site) }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    spinner.onItemSelected {
      labelSite.showOrHide(it > 0)
      inputSite.showOrHide(it > 0)
    }

    spinner.adapter = ArrayAdapter(
        this,
        R.layout.list_item_spinner,
        arrayOf("Select an option…", "I do not have a website", "I have a website!")
    ).apply {
      setDropDownViewResource(R.layout.list_item_spinner_dropdown)
    }

    form {
      useRealTimeValidation(disableSubmit = true)

      inputLayout(R.id.input_layout_name, name = "Name") {
        isNotEmpty().description("Enter your name!")
        length().atLeast(3)
      }

      input(R.id.input_email, name = "Email") {
        isNotEmpty()
        isEmail()
      }

      input(R.id.input_age, name = "Age") {
        isNotEmpty()
        isNumber()
      }

      input(R.id.input_weight, name = "Weight") {
        isNotEmpty()
        isDecimal().atMost(9999.99)
      }

      spinner(R.id.input_spinner, name = "Have a website") {
        selection()
            .atLeast(1)
            .description("Please make a selection")
        onErrors { _, errors ->
          val firstError = errors.firstOrNull()
          spinnerError.showOrHide(firstError != null)
          spinnerError.text = firstError?.toString()
        }
      }

      input(R.id.input_site, name = "Site", optional = true) {
        conditional({ spinner.selectedItemPosition > 1 }) {
          isUrl()
        }
      }

      input(R.id.input_bio, name = "Biography") {
        isNotEmpty()
        length().atLeast(20)
      }

      seeker(R.id.seek_bar, name = "Seek Bar") {
        progress()
            .atLeast(50)
            .description("selection must be at least 50%")
        onErrors { _, errors ->
          val firstError = errors.firstOrNull()
          seekBarError.showOrHide(firstError != null)
          seekBarError.text = firstError?.toString()
        }
      }

      checkable(R.id.check_terms, name = "Terms Agreement") {
        isChecked()
        onErrors { _, errors ->
          val firstError = errors.firstOrNull()
          checkBoxError.showOrHide(firstError != null)
          checkBoxError.text = firstError?.toString()
        }
      }

      submitWith(R.id.submit) { result ->
        toast("Success! Hello ${result["Name"]?.value}")
      }
    }
  }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

3. bloderxd/blitz

Android real time form validator in a nice Kotlin DSL.

Blitz is an Android real time form validator using a nice Kotlin DSL.

Usage

With Blitz you can validate in real time a entire form, let's create an example
For this example let's create a sign up form, in this form we just have an email and a document fields and a accept terms check box. First let's just consider that we want to enable the sign up button only when user writes a correct email in email field and fill with some value the document field, for this we can use the blitz core default validations:

signup.enableWhen {
    email_field.isEmail()
    document_field.isFilled()
}

blitz Example Tutorial

Really simple, isn't? But now we want to improve some validations, we have a terms check box that is important in our form validation then we want to add in our validations a validation that user has selected the terms check box, for this let's use custom validations:
First, we need to create our validations class, let's extend it from Blitz core defaut validations to get all the default validations:

class MyCustomValidations : DefaultBlitzValidations() {

    fun CheckBox.isAccepted() : View = bindViewValidation(this) {
        this.isChecked
    }
}

then we just need to tell Blitz what validations it must use:

signup.enableWhenUsing(MyCustomValidations()) {
    email_field.isEmail()
    document_field.isFilled()
    terms.isAccepted()
}

blitz Example Tutorial

Masks

In a form creation some times masks are important, thinking on that Blitz comes with an API for numeric masks, let's use our sign up example again:
Now we want to apply a mask in document field, let's consider this as a valid document 123.456.789-0, Blitz mask api works basically considering the character # as a number and the rest of it part of the mask design then the correct design for our mask should be #<code>#</code><code>#</code>.<code>#</code><code>#</code><code>#</code>.<code>#</code><code>#</code>#-#:

signup.enableWhenUsing(MyCustomValidations()) {
    email_field.isEmail()
    document_field.isFilled() withMask "###.###.###-#"
    terms.isAccepted()
}

Just that!

blitz Example Tutorial

Success and error cases

The best forms are those that can handle every error case from each field and Blitz provides an API for that too. Let's create a simple error and success handling for our sign up form. Now when user writes a valid email I want to show a check icon and when user writes a not valid email I want to show an alert icon:

private fun showSuccessCaseFor(successView: View, errorView: View) {
    successView.visibility = View.VISIBLE
    errorView.visibility = View.GONE
}

private fun showErrorCaseFor(successView: View, errorView: View) {
    successView.visibility = View.GONE
    errorView.visibility = View.VISIBLE
}

fun main() = signup.enableWhenUsing(CustomValidationExample()) {
    email_field.isEmail() onValidationSuccess {
        showSuccessCaseFor(email_success_icon, email_error_icon)
    } onValidationError {
        showErrorCaseFor(email_success_icon, email_error_icon)
    }
    document_field.isFilled() withMask "###.###.###-#"
    terms.isAccepted()
}

onValidationSuccess and onValidationError are functions that provides the validation state of each field.

blitz Example Tutorial

Installation

Gradle

implementation 'bloder.com:blitz:0.0.1'

Maven

<dependency>
    <groupId>bloder.com</groupId>
    <artifactId>blitz</artifactId>
    <version>0.0.1</version>
    <type>pom</type>
</dependency>

Full Example

Follow these steps to create a full example based on this tutorial:

Step 1. Design Layouts

We need to design our XML layouts as follows:

(a). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/email_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginRight="40dp"
        android:layout_marginLeft="30dp"
        android:layout_marginBottom="30dp"
        android:layout_marginTop="30dp"
        android:hint="E-mail"/>

    <EditText
        android:id="@+id/document_field"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginRight="40dp"
        android:layout_marginLeft="30dp"
        android:layout_marginBottom="30dp"
        android:layout_marginTop="30dp"
        android:layout_below="@+id/email_field"
        android:hint="Document"/>

    <CheckBox
        android:id="@+id/terms"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/document_field"
        android:layout_marginLeft="30dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="I accept the terms and blablabla"
        android:layout_marginTop="217dp"
        android:layout_marginLeft="60dp"/>

    <Button
        android:id="@+id/signup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/terms"
        android:layout_marginTop="30dp"
        android:background="@drawable/bg_button"
        android:text="Sign up"
        android:textColor="@drawable/text_selector" />

    <ImageView
        android:id="@+id/email_success_icon"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:src="@drawable/check_icon"
        android:layout_alignRight="@+id/email_field"
        android:layout_marginTop="40dp"
        android:visibility="gone"/>

    <ImageView
        android:id="@+id/email_error_icon"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:src="@drawable/error_icon"
        android:layout_alignRight="@+id/email_field"
        android:layout_marginTop="40dp"
        android:visibility="gone"/>

    <ImageView
        android:id="@+id/document_success_icon"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:src="@drawable/check_icon"
        android:layout_alignRight="@+id/document_field"
        android:layout_marginTop="150dp"
        android:visibility="gone"/>

    <ImageView
        android:id="@+id/document_error_icon"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:src="@drawable/error_icon"
        android:layout_alignRight="@+id/document_field"
        android:layout_marginTop="145dp"
        android:visibility="gone"/>

</RelativeLayout>

Step 2. Write Code

Finally we need to write our code as follows:

(a). CustomValidationExample.kt

package bloder.com.blitz

import android.view.View
import android.widget.CheckBox
import bloder.com.blitzcore.validation.DefaultBlitzValidations

class CustomValidationExample : DefaultBlitzValidations() {

    fun CheckBox.isAccepted() : View = bindViewValidation(this) {
        this.isChecked
    }
}

(b). MainActivity.kt

package bloder.com.blitz

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Toast
import bloder.com.blitzcore.enableWhen
import bloder.com.blitzcore.enableWhenUsing
import bloder.com.blitzcore.mask.withMask
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        buildCustomFormValidation()
        signup.setOnClickListener {
            Toast.makeText(this, "Signed up", Toast.LENGTH_LONG).show()
        }
    }

    private fun showSuccessCaseFor(successView: View, errorView: View) {
        successView.visibility = View.VISIBLE
        errorView.visibility = View.GONE
    }

    private fun showErrorCaseFor(successView: View, errorView: View) {
        successView.visibility = View.GONE
        errorView.visibility = View.VISIBLE
    }

    private fun buildCustomFormValidation() = signup.enableWhenUsing(CustomValidationExample()) {
        email_field.isEmail() onValidationSuccess {
            showSuccessCaseFor(email_success_icon, email_error_icon)
        } onValidationError {
            showErrorCaseFor(email_success_icon, email_error_icon)
        }
        document_field.isFilled() withMask "###.###.###-#"
        terms.isAccepted()
    }

    private fun buildFormValidation() = signup.enableWhen {
        email_field.isEmail()
        document_field.isFilled()
    }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

4. ibrahimsn98/Freya

A lightweight, simplified form validation library for Android.

A lightweight, simplified form validation library for Android.

Freya Example Tutorial
Freya Example Tutorial

Screenshots

Freya Example Tutorial

Validation Rules Setup

<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:app="http://schemas.android.com/apk/res-auto">

    <formField
        app:id="@id/inputUsername"
        app:required="true"
        app:minSize="3"
        app:maxSize="25" />

    <formField
        app:id="@id/inputEmail"
        app:email="true" />

    <formField
        app:id="@id/inputPhone"
        app:required="true"
        app:phoneNumber="true" />

    <formField
        app:id="@id/inputPassword"
        app:required="true"
        app:minSize="3"
        app:maxSize="25" />

</resources>

Form Layout Setup

<me.ibrahimsn.lib.FreyaForm
    android:id="@+id/freya"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:config="@xml/form_app">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/inputEmail"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="12dp">

        <com.google.android.material.textfield.TextInputEditText
            android:inputType="textEmailAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Email Address"/>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/inputPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:passwordToggleEnabled="true"
        android:layout_marginBottom="16dp">

        <com.google.android.material.textfield.TextInputEditText
            android:inputType="textPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Password"/>

    </com.google.android.material.textfield.TextInputLayout>

</me.ibrahimsn.lib.FreyaForm>

Validation on Form Submit

val freya = findViewById<FreyaForm>(R.id.freya)
val submitButton = findViewById<Button>(R.id.submit)

/*
 * Listens if the whole form is valid
 */
freya.onValidationChangeListener = {
    Log.d(TAG, "Is form valid: $it")
}

/*
 * Listens for form validation errors after validate()
 */
freya.onErrorListener = {
    Log.d(TAG, "Validation errors: $it")
}

submitButton.setOnClickListener {
    Log.d(TAG, "Is form valid: ${freya.validate()} values: ${freya.values}")
}

Realtime Field Validation

/*
 * Listens for validation changes of any form field
 */
freya.onFieldValidationChangeListener = {
    when (it.error) {
        is Ruler.Required -> {
            it.setError("This field is required.")
        }
        is Ruler.Email -> {
            it.setError("Please enter a valid email address.")
        }
        is Ruler.PhoneNumber -> {
            it.setError("Please enter a valid phone number.")
        }
        is Ruler.MinSize -> {
            it.setError("This field must contain at least ${it.error?.param} characters.")
        }
        is Ruler.MaxSize -> {
            it.setError("This field must contain at most ${it.error?.param} characters.")
        }
        is Ruler.Regex -> {
            it.setError("Please provide a valid data.")
        }
    }
}

Get Values & Prefill Form

val values: Map<Int, Any?> = freya.values

freya.setup(
    mapOf(
        R.id.inputUsername to "Thomas"
    )
)

Note

Download Demo APK

Setup

Follow me on Twitter @ibrahimsn98
Step 1. Add the JitPack repository to your build file

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
    implementation 'com.github.ibrahimsn98:freya:1.0.0'
}

Full Example

Let us look at a full android sample project.

Step 1. Design Layouts

We need to design our XML layouts as follows:

(a). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="22dp"
    tools:context=".MainActivity">

    <me.ibrahimsn.lib.FreyaForm
        android:id="@+id/freya"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:config="@xml/form_app">

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/inputUsername"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp">

            <com.google.android.material.textfield.TextInputEditText
                android:inputType="text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Username"/>

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/inputEmail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp">

            <com.google.android.material.textfield.TextInputEditText
                android:inputType="textEmailAddress"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Email Address"/>

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/inputPhone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp">

            <com.google.android.material.textfield.TextInputEditText
                android:inputType="textEmailAddress"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Phone Number"/>

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/inputPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:passwordToggleEnabled="true"
            android:layout_marginBottom="16dp">

            <com.google.android.material.textfield.TextInputEditText
                android:inputType="textPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Password"/>

        </com.google.android.material.textfield.TextInputLayout>

        <Button
            android:id="@+id/submit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="Submit Form" />

    </me.ibrahimsn.lib.FreyaForm>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 2. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

package me.ibrahimsn.freya

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import me.ibrahimsn.lib.FreyaForm
import me.ibrahimsn.lib.api.rule.Ruler

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val freya = findViewById<FreyaForm>(R.id.freya)
        val submit = findViewById<Button>(R.id.submit)

        /*
         * Listens if the whole form is valid
         */
        freya.onValidationChangeListener = {
            Log.d(TAG, "Is form valid: $it")
        }

        /*
         * Listens for validation changes of any form field
         */
        freya.onFieldValidationChangeListener = {
            when (it.error) {
                is Ruler.Required -> {
                    it.setError("This field is required.")
                }
                is Ruler.Email -> {
                    it.setError("Please enter a valid email address.")
                }
                is Ruler.PhoneNumber -> {
                    it.setError("Please enter a valid phone number.")
                }
                is Ruler.MinSize -> {
                    it.setError("This field must contain at least ${it.error?.param} characters.")
                }
                is Ruler.MaxSize -> {
                    it.setError("This field must contain at most ${it.error?.param} characters.")
                }
                is Ruler.Regex -> {
                    it.setError("Please provide a valid data.")
                }
            }
        }

        /*
         * Listens for form validation errors after validate()
         */
        freya.onErrorListener = {
            Log.d(TAG, "Validation errors: $it")
        }

        /*
         * Pre-fills form fields
         */
        freya.setup(
            mapOf(
                R.id.inputUsername to "Thomas"
            )
        )

        submit.setOnClickListener {
            Log.d(TAG, "Is form valid: ${freya.validate()} values: ${freya.values}")
        }
    }

    companion object {
        private const val TAG = "mainActivity"
    }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.