Plantilla con validación de campos


Plantilla para crear un formulario funcional, petición de datos y con validación de campos en tiempo de escritura.

En esta plantilla se incluye lo siguiente:

  • LinearLayout
  • TextInputLayout
  • TextInputEditText
  • AppCompatButton
  • ValidatorFieldHelper
  • ValidationMainFields
  • Extensiones Kotlin

Contenedor

Layout content_main.xml conformado con

<?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"
    android:padding="16dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main"
    tools:context=".MainActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/til1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/edtEmail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/validate_email"
            android:imeOptions="actionNext"
            android:inputType="textEmailAddress"
            android:singleLine="true" />

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


    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/til2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/edtUserName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentStart="true"
                android:ellipsize="end"
                android:hint="@string/validate_username"
                android:imeOptions="actionNext"
                android:inputType="textPersonName"
                android:paddingEnd="50dp"
                android:singleLine="true">

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

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/btnAutoUsername"
                android:layout_width="44dp"
                android:layout_height="44dp"
                android:layout_alignParentEnd="true"
                android:layout_marginEnd="2dp"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:clickable="true"
                android:focusable="true"
                android:padding="10dp"
                app:srcCompat="@drawable/ic_gavel_black_24dp" />
        </RelativeLayout>
    </com.google.android.material.textfield.TextInputLayout>


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

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/edtPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/validate_password"
            android:imeOptions="actionNext"
            android:inputType="textPassword"
            android:singleLine="true" />

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

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

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/edtCofirmPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/validate_password_confirm"
            android:imeOptions="actionDone"
            android:inputType="textPassword"
            android:singleLine="true" />

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

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnValidate"
        style="@style/Widget.MaterialComponents.Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16dp"
        android:focusable="true"
        android:text="@string/validate_btn" />

</LinearLayout>

ValidatorFieldHelper

ValidatorFieldHelper.kt Ayuda para la verificación de campos

abstract class ValidatorFieldHelper {

    interface ValidatorFieldsListener {
        fun onSuccessfulValidator()
        fun onErrorValidator(assertionList: HashMap<String, AssertionItem>)
    }

    data class AssertionItem(val isValid: Boolean, val error: String? = null)

    private var listener: ValidatorFieldsListener? = null

    open var isValid: Boolean = false

    private val assertionList: HashMap<String, AssertionItem> = hashMapOf()

    //Append new field
    fun addAssertion(key: String) {
        assertionList[key] = AssertionItem(false)
    }

   //Get a validation field
    fun getAssertion(key: String): AssertionItem? {
        return assertionList[key]
    }

    //Update a validation field
    fun setAssertion(key: String, value: AssertionItem) {
        assertionList[key] = value
    }

    //Run validate all fields
    fun validate() {
        isValid = false
        if (assertionList.size == 0) return
        isValid = true
        assertionList.forEach {
            if (!it.value.isValid) isValid = false
        }

        if (isValid) listener?.onSuccessfulValidator() else listener?.onErrorValidator(assertionList)

    }

    fun setOnValidateListener(listener: ValidatorFieldsListener) {
        this.listener = listener
    }

}

ValidatorMainFields

ValidationMainFields.kt validar los campos del formulario

class ValidationMainFields(private val context: Context) : ValidatorFieldHelper() {

    init {
        addAssertion(FIELD_EMAIL)
        addAssertion(FIELD_USER_NAME)
        addAssertion(FIELD_PASSWORD)
        addAssertion(FIELD_CONFIRM_PASSWORD)
    }

    fun checkFieldEmail(s: String): AssertionItem? {
        var isValid = false
        var errorMsg: String? = null
        if (s.isNotEmpty()) {
            if (isValidEmail(s)) {
                isValid = true
            } else {
                errorMsg = context.getString(R.string.error_email)
            }
        } else {
            errorMsg = context.getString(R.string.error_empty)
        }

        setAssertion(FIELD_EMAIL, AssertionItem(isValid, errorMsg))
        return getAssertion(FIELD_EMAIL)
    }

    fun checkFieldUserName(s: String): AssertionItem? {
        var isValid = false
        var errorMsg: String? = null
        if (s.isNotEmpty()) {
            if (isUniqueUserName(s)) {
                isValid = true
            } else {
                errorMsg = context.getString(R.string.error_username)
            }
        } else {
            errorMsg = context.getString(R.string.error_empty)
        }

        setAssertion(FIELD_USER_NAME, AssertionItem(isValid, errorMsg))
        return getAssertion(FIELD_USER_NAME)

    }

    fun checkFieldPassword(s: String): AssertionItem? {
        var isValid = false
        var errorMsg: String? = null
        if (s.isNotEmpty()) {
            isValid = true
        } else {
            errorMsg = context.getString(R.string.error_empty)
        }

        setAssertion(FIELD_PASSWORD, AssertionItem(isValid, errorMsg))
        return getAssertion(FIELD_PASSWORD)
    }

    fun checkFieldConfirmPassword(password1: String, password2: String): AssertionItem? {
        var isValid = false
        var errorMsg: String? = null
        if (password2.isNotEmpty()) {
            isValid = password1 == password2
            if (!isValid) errorMsg = context.getString(R.string.error_password_confirm)
        } else {
            errorMsg = context.getString(R.string.error_empty)
        }

        setAssertion(FIELD_CONFIRM_PASSWORD, AssertionItem(isValid, errorMsg))
        return getAssertion(FIELD_CONFIRM_PASSWORD)
    }


    private fun isValidEmail(target: CharSequence): Boolean {
        return if (TextUtils.isEmpty(target)) {
            false
        } else {
            Patterns.EMAIL_ADDRESS.matcher(target).matches()
        }
    }

    private fun isUniqueUserName(s: String): Boolean {
        return s != "bob"
    }


    companion object {
        const val FIELD_EMAIL = "FIELD_EMAIL"
        const val FIELD_USER_NAME = "FIELD_USER_NAME"
        const val FIELD_PASSWORD = "FIELD_PASSWORD"
        const val FIELD_CONFIRM_PASSWORD = "FIELD_CONFIRM_PASSWORD"
    }
}

MainActivity

MainActivity.kt parte funcional de entrada de datos

class MainActivity : AppCompatActivity() {

    private val myValidator = ValidationMainFields(this)

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

        btnAutoUsername.setOnClickListener {
            val s = "bob" + (0..100).random()
            edtUserName.setText(s)
        }

        myValidator.setOnValidateListener(validatorListener())

        edtEmail.onChangeDebounce {
            myValidator.checkFieldEmail(it)
            myValidator.getAssertion(ValidationMainFields.FIELD_EMAIL)?.let {
                til1.error = it.error
            }
        }

        edtUserName.onChangeDebounce {
            myValidator.checkFieldUserName(it)
            myValidator.getAssertion(ValidationMainFields.FIELD_USER_NAME)?.let {
                til2.error = it.error
            }
        }

        edtPassword.onChangeDebounce {
            myValidator.checkFieldPassword(it)
            myValidator.getAssertion(ValidationMainFields.FIELD_PASSWORD)?.let {
                til3.error = it.error
            }
        }
        edtCofirmPassword.onChangeDebounce {
            myValidator.checkFieldConfirmPassword(edtPassword.text.toString(), it)
            myValidator.getAssertion(ValidationMainFields.FIELD_CONFIRM_PASSWORD)?.let {
                til4.error = it.error
            }
        }


        btnValidate.setOnClickListener {
            myValidator.checkFieldEmail(edtEmail.text.toString())
            myValidator.checkFieldUserName(edtUserName.text.toString())
            myValidator.checkFieldPassword(edtPassword.text.toString())
            myValidator.checkFieldConfirmPassword(edtPassword.text.toString(), edtCofirmPassword.text.toString())
            myValidator.validate()
        }

        edtCofirmPassword.onDone {
            btnValidate.performClick()
        }

    }

    private fun validatorListener(): ValidatorFieldHelper.ValidatorFieldsListener {
        return object : ValidatorFieldHelper.ValidatorFieldsListener {
            override fun onSuccessfulValidator() {
                toast("onSuccessfulValidator")
                til1.error = null
                til2.error = null
                til3.error = null
                til4.error = null
            }

            override fun onErrorValidator(assertionList: HashMap<String, ValidatorFieldHelper.AssertionItem>) {
                toast("Validator error")
                Log.w(TAG, "onErrorValidator() called with: assertionList = [$assertionList]")

                assertionList[ValidationMainFields.FIELD_EMAIL]?.let {
                    til1.error = it.error
                }
                assertionList[ValidationMainFields.FIELD_USER_NAME]?.let {
                    til2.error = it.error
                }
                assertionList[ValidationMainFields.FIELD_PASSWORD]?.let {
                    til3.error = it.error
                }
                assertionList[ValidationMainFields.FIELD_CONFIRM_PASSWORD]?.let {
                    til4.error = it.error
                }

            }

        }
    }

    //Kotlin extensions

    fun Context?.toast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) =
        this?.let { Toast.makeText(it, text, duration).show() }

    fun Context?.toast(@StringRes textId: Int, duration: Int = Toast.LENGTH_SHORT) =
        this?.let { Toast.makeText(it, textId, duration).show() }

    fun TextInputEditText.onChange(cb: (String) -> Unit) {
        this.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                cb(s.toString())
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
        })
    }

    fun TextInputEditText.onChangeDebounce(duration: Long = 350L, cb: (String) -> Unit) {
        var lastStr = ""
        this.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {}
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                val newStr = s.toString()
                if (newStr == lastStr)
                    return
                lastStr = newStr
                GlobalScope.launch(Dispatchers.Main) {
                    delay(duration)
                    if (newStr != lastStr)
                        return@launch
                    if (isAttachedToWindow) cb(s.toString())
                }
            }
        })
    }

    fun TextInputEditText.onDone(callback: () -> Unit) {
        setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                callback.invoke()
            }
            false
        }
    }

    companion object {
        private val TAG = MainActivity::class.java.simpleName
    }

}

Muestra de la plantilla

Publicado por Codelaby

Mobile DevDesigner

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: