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.addField()
as many fields you would like to have in form; - 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 emailField = FormField(
emailContainer.id,
emailChanges,
emailValidations
)
val passwordField = FormField(
passwordContainer.id,
passwordChanges,
passwordValidations
)
val form = Form.Builder<Int>()
.setFieldValidationListener(this)
.setFormValidationListener(this)
.setValidSubmitListener(this)
.setSubmitFailedListener(this)
.addField(emailField)
.addField(passwordField)
.build()
submit.setOnClickListener {
form.doSubmit()
}
Full sample here
RxForm README
LiveDataForm README
- It's an ID to identify uniquely the field in a form. Of type
T
.
- It's an
IObservableValue<R>
used to feed the field value change events to the form.
- It's an
IObservableValue<List<ValidationMessage>>
, callIObservableValue.addChangeListener()
to add aChangeObserver
. This is where the form will deliver the validation changes events for this field.
- It's an
IObservableValue<Boolean>
, use this to enable or disable the form validation and submission for this field. - Setting it with
True
will cause a field validation if the form strategy allows. - Setting it with
False
will cause theFormField.errors
to be called with an emptyList if the form strategy validation allows. - Setting it with
False
ornull
will stop calling its validators and remove this field from the form submission data.
- It's a list of
Validator<R>
. Each validator will be called in order to process a round of validation for this field.
- It's a list of
IObservableChange<Unit>
used to force a round of validation to happen to this field if the form strategy allows.
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.
- It's triggered when a given field validation state change. This callback gives access to a
Pair<T,List<ValidationMessage>>
if the list is empty, the field identified by the keyT
is valid. - This callback allows you to set/unset a error message for each screen field of a form.
- It has the same behavior of
FormField.errors
.
- It's 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 allows you to enable or disable a submit button or hint the user about the form state.
- It's 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.
- You also can use
FormField.input.value
to read the current value of a field when submitting.
- It's 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 can be used 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 validators 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
.
A ValidationMessagetakes a
messageto be shown to the user when the validation fails and a
ValidationTypethat 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 {
input ?: return false
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: String, input: String, output: String) = output.length
override fun format(input: String): String {
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 String.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.7.0</version>
</dependency>
or Gradle:
add
maven { url 'https://oss.sonatype.org/content/groups/public' }
and
implementation 'br.com.youse.forms:form-jdk:0.7.0'
implementation 'br.com.youse.forms:rx-form-jdk:0.7.0'
implementation 'br.com.youse.forms:validators-jdk:0.7.0' // optional
implementation 'br.com.youse.forms:formatters-jdk:0.7.0' // optional