Form makes it easy to format and validate form fields and submit the form when every thing is ready.
And it come in many flavors:
- Form for the 100% pure Kotlin library;
- RxForm for RxJava lovers;
- LiveDataForm for DataBinding/LiveData addicts;
- Create a
Form.Builder<T>()
whereT
is the type of each field key; - Set the callbacks for the validation events, like
builder.setFieldValidationListener()
orbuilder.setValidSubmitListener()
; - Call
builder.addFieldValidations()
as many fields in the form using the parameters:- a key of type T;
- an
IForm.ObservableValue<R>
orIForm.DeferredObservableValue<R>
; - a list of Validator;
- Execute the
builder.build()
method to get a hold of aForm
instance; - Every time the user requests to submit the form, call the method
form.doSubmit()
;
In the sample below we use Int
as T
because we are using the Android R.id.$viewId Int
as keys
and String
as R
since email
and password
are EditTexts.
val emailChanges = ObservableValue(email.text.toString())
val passwordChanges = ObservableValue(password.text.toString())
email.addTextChangedListener(getTextWatcher(emailChanges))
password.addTextChangedListener(getTextWatcher(passwordChanges))
val form = Form.Builder<Int>()
.setFieldValidationListener(this)
.setFormValidationListener(this)
.setValidSubmitListener(this)
.setSubmitFailedListener(this)
.addFieldValidations(emailContainer.id,
emailChanges, emailValidations)
.addFieldValidations(passwordContainer.id,
passwordChanges,
passwordValidations)
.build()
submit.setOnClickListener {
form.doSubmit()
}
Full sample here
RxForm README
LiveDataForm README
There are four standard callbacks in every flavor of Form, depending on the form flavor you choose to use the events are delivered as a pure Kotlin callbacks, an Observable
emissions or LiveData
events.
Does not matter the flavor, the behavior and naming should be consistent enough to understand what is happening.
- Is triggered when a given field validation state change. This callback gives access to a
List<ValidationMessage>
that if it is empty, the field is valid. This callback allows you to set/unset a error message for each field in the form.
- Is triggered when the form validation state change. A boolean is used to indicate if the form is valid as a whole or not. This callback allow you to enable or disable a submit button or hint the user about the form state.
- Is triggered when a valid submit happens. This callback gives access to a
List<Pair<T, Any>>
whereT
is a field key, and theAny
is that field current value. This callback is called to allow you to send the form data to your server.
- Is triggered when a submit happens but the form is not valid. This callback gives access to a
List<Pair<T, List<ValidationMesage>>>
whereT
is a field key andList<ValidationMessage>
indicates how many validations failed for that given field. This callback is used when you need to scroll to a invalid field after a submit.
As an optional dependency we offer the possibility to use our validators module.
A form validation is related to each field validation,
so we need to define a list of validations for each field in the form.
You can define your own validators by implementing the Validator<T>
interface,
in the sample below we define an HoursValidator
and a ValidationMessage
. It takes a message
to be shown
to the user when the validation fails and a ValidationType
that was created to
specify which type of validation failed (you can have multiple validators with the same ValidationType
).
Note that your form can validate any type of input, here we used String
,
but your implementation can be of any class that you define and implements the isValid
method.
class HoursValidator(val message: String, val divider: String) : Validator<String> {
override fun validationMessage(): ValidationMessage {
return ValidationMessage(message = message, validationType = HOUR_FORMAT)
}
override fun isValid(input: String): Boolean {
return try {
val parts = input.split(divider)
val hours = parts.firstOrNull()?.toInt() ?: -1
val minutes = parts.lastOrNull()?.toInt() ?: -1
(parts.size == 2
&& hours >= 0
&& hours <= 23
&& minutes >= 0
&& minutes <= 59)
} catch (e: Throwable) {
false
}
}
}
As an optional dependency we offer the possibility to use our formatters module. Usually a formatter is coupled with the field validation. A field that displays hour and minutes (HH:MM) could be valid if the the hours and minutes are valid only if in a given format. So a given formatter is used together with a given validator.
// 00:00
class HoursFormatter(val divider: String) : TextFormatter {
private val digitsOnlyRegex = "[^0-9]".toRegex()
override fun getCursorPosition(previous: CharSequence, input: CharSequence, output: CharSequence) = output.length
override fun format(input: CharSequence): CharSequence {
val clearText = input.toDigitsOnly()
return when (clearText.length) {
0, 1 -> clearText
2, 3, 4 -> clearText.substring(0, 2) + divider + clearText.substring(2)
else -> clearText.substring(0, 2) + divider + clearText.substring(2, 4)
}
}
private fun CharSequence.toDigitsOnly(): String {
return replace(digitsOnlyRegex, "")
}
}
Note that each implementation of TextFormatter
has a getCursorPosition()
method that should return
where the field cursor should be positioned after each change in the input field. A more general interface Formatter<T>
can be used
when the cursor position is not important or possible to be implemented.
Download or grab via Maven:
<dependency>
<groupId>br.com.youse.forms</groupId>
<artifactId>form-jdk</artifactId>
<version>0.0.1</version>
</dependency>
or Gradle:
add
maven { url 'https://oss.sonatype.org/content/groups/public' }
and
implementation 'br.com.youse.forms:form-jdk:0.0.1'
implementation 'br.com.youse.forms:rx-form-jdk:0.0.1'
implementation 'br.com.youse.forms:validators-jdk:0.0.1' // optional
implementation 'br.com.youse.forms:formatters-jdk:0.0.1' // optional