Mittwoch, 22. Januar 2014

BARACUS from Scratch - Part 6 - Automatic Form Validation (Chapter one of Two)

Table of Contents

Previous Tutorial : Advanced Persistence Mapping

In this tutorial I am going to explain You the benefits of automated form validation. This feature is essiential to me to have valid data in a form in order to edit or add database content in my applications. Basically, I adopted the concept of form validation from jsf. You can register named validators (a variety of validators already is defined) and declare them to be applied on Your component. Because all data is held in TextViews, there currently only exists a ConstrainedEditText component but it is easy to extend this system.


The Basics I - Validators and the ValidationFactory

Imagine You have a pool of validators registered in Your application. Each validator is capable to determine that a information passed to it is valid or not. Additionally, each validator has a unique name which makes it identifiable within the container. A validator is passed to the ValidationFactory using either a name or not; if You don't supply a name, the name will be the class simple name with a small capital first character (FooValidator -> fooValidator). Because You can pass fully instantiated class instances to the registry, You also use Beans (and all the luxuries of a managed bean) for validation tasks.

The ValidatorFactory has a second task. It is able to perform an explicit validation on a view. It does this by recursively iterating the form and looking for instances of the ConstrainedView interface. When it finds a constrained view implementation, it takes the validators named in the app:validatedBy field and tries to validate the information.

Anything implementing the ConstrainedView interface can be used to validate a form.

There are already a set of predefined validators ready-to-use :
  • DateFromNow
  • NumberMustBeGreaterThanZero
  • StringIsNumericDouble
  • StringIsNumericInteger
  • StringNotEmpty
There will be more validators in future soon. These validators perform basic (atomic) validations of a single attribute and are ready to use.

Every validator needs to know about the error message to route. This is a text information placed in the strings.xml. The built in validators have only english texts at the moment (any extension help welcome :)).

The Basics II - Declaring the use of a validator

Now this is easy. The org.baracus.ui.ConstrainedEditText commponent carries an attribute named app:validatedBy. This attribute carries a comma seperated list of all validator names to be applied. The order of the validators in the string is the order the validation is done. So in order to check the component not to have empty text, the xml declaration looks like this : 

<org.baracus.ui.ConstrainedEditText android:id="@+id/txtFirstName"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              app:validatedBy="stringNotEmpty"
            />

That's it. Because the validation is runtime-evaluated, You have to manually check that validation works at least once; the validation factory will produce an exception if an unidentifiable validator is used. The validators are seperated by using a comma.

The Basics III - choosing the validation strategy

There are three strategies for automated form validation: validating explicitly (routing the errors manually placing your validation code into the controller), implicitely using the ApplicationContext's or, third way,  focusChangedBasedValidation automatic validation using a ManagedFragment or a ManagedActivity.

The Application context offers a number of methods for validation (depending on the strategy You choose).

Explicit, controller based validation without validator beans

The first solution enters the game, if you want to validate e.g. on button click having complex validation logics in Your controller. This strategy does not need any controller bean; your controller does all the validation logics

Then You can define the explicit validation in Your controller:

public void btnSaveClicked(View v){

        // clear all error notifications, we are going to validate here
        ApplicationContext.resetErrors(underlyingView);

        // explicit form validation
        if (accountNumber.getText() == null || accountNumber.getText().toString().trim().length() ==0) {
            ApplicationContext.addErrorToView(underlyingView, R.id.txtAccountNumber, R.string.notNullAccountNumber, ErrorSeverity.ERROR);
            // ... here could be more validations
            // ... and here we route them to the form :
            ApplicationContext.applyErrorsOnView(underlyingView);
        } else {
            currentAccount.setAccountNumber(accountNumber.getText().toString());
            currentAccount.setAccountName(accoutName.getText().toString());
            accountDao.save(currentAccount); // This will fire the DataChanged Event on the MainActivity
            onBackPressed();
        }
    }

As You can see, every errors are routed explicitly to form fields. Finally, You can order the context to apply the errors to the fields.

Implicit, controller based validation

Instead of relying on controller logics, you can attach - like shown in basics I - named validators to the component and let the ApplicationContext do the controller logics.

public void btnSaveClicked(View v){

        // clear all error notifications, we are going to validate here
        ApplicationContext.resetErrors(underlyingView);

        /* implicit form validation
         * The validation wiring is done on the validatedBy-Property inside of
         * the xml-declaration of the components
         * The routing is determined by the use of special error views (case 1)
         * or a mappeable component (TextViews are processed by @see TextEditErrorHandlers) */
        ApplicationContext.validateView(underlyingView);

        if (!ApplicationContext.viewHasErrors(underlyingView)) {
            currentCustomer.setFirstName(firstName.getText().toString());
            currentCustomer.setLastName(lastName.getText().toString());
            customerDao.save(currentCustomer);
            onBackPressed();
        }

    }


So all the dirty work of routing error messages is done by calling validateView for the form view. Before doing this, all existing errors should be removed. The validation itself is done via the above mentioned validators. These itchy little helpers get applied to the form and are used to decide, whether an error message has to be routed to the component or not.

Fully automatic validation

You also can use fully automatic validation. Therefore, Your Activity must be inheriting the baracus' ManagedActivity - or if fragments are used - the ManagedFragment. Both components offer the enableFocusChangeBasedValidation function in order to manage a validation, when a ui component focus changes.

This will enable You to have automatic validation and error routing to all your constrained components whenever a focus changes (this also includes the removal of errors from corrected input fields).

Any further - more or less complex - validation must be implemented by hand.

The Basics IV - Message routing

If you simply attach the validator to Your input field, baracus will make use of the standard android error handling function. But there is another trick You might use for handling complex error output. Therefore You can define a custom error view, baracus will use instead of the standard error handling. A custom error handler is defined like this :

    <org.baracus.ui.ErrorView android:id="@+id/customerFirstMsg"
                                       app:displayMessageFor="@id/txtCustomerFirstName"
                                       app:highlightTarget="true"
                                       android:layout_width="wrap_content"
                                       android:layout_height="wrap_content"
                                       android:layout_marginLeft="10dp"
                                       android:editable="true"
                                       android:textColor="#FF0000"
                                       android:lines="1"
                                       android:text=""/>

with the displayMessageFor-information You tell baracus, that this specific fields is responsible for the error handling of @id/txtCustomerFirstName.

with the highlightTarget-informatrion You tell baracus to highlight the error source field when a message is displayed.

This technique is useful e.g. if You want to display an error image instead of textual information.

Conclusion

With the three mechanisms of error handling, field validation and error routing, You get a powerful tools to make form-based android development more comfortable - no more writing of in-controller boilerplate validation code. You can make benefit of the use of named validators and the possibility to reuse validation code.

Best practice : I found for myself making ManagedBeans out of all my fragments has proven to be a good choice to make development more comfortable. You get the luxury of dependency injection inside of Your fragments, and You can make use of all container functions baracus offers. Therefore, You MUST used ManagedFragments (It is an android malice to make internal re-instantiation of fragments on device rotation); otherwise all injected fields are nulled!

COMING SOON : In the next tutorial I am going to show You, how to implement these validation techniques in the demo app.