19

I am new to android mobile development (Android Studio native development - for new knowledge). And here I want to ask a question regarding best practice for input validation. As far we know, when a developer develop input form. We need to prevent a user from key in a wrong input into the text field. So here is my question,

  1. Can we create one java file for validation purpose only? All input form ,must go to that one validation file only (In case of many input page screen in one apps). If YES, how can I get an example/link/tutorial of that technique for my learning study. If NO, why?

From my point of view personally, it should have a way to implement the technique. So that we didn't need to reuse same code all over again for each java file (in term of clean code). Unfortunately, I didn't find any example or tutorial for that. Maybe I search a wrong keyword or misread. And if there is no such technique exist, what are the best practice for input validation?

Thank you.

p/s: This thread for find a better way in best practice. Thank you.

Salehin Rafi
  • 351
  • 2
  • 6
  • 16
  • Actually there are some libs already https://github.com/vekexasia/android-edittext-validator and https://github.com/thyrlian/AwesomeValidation – Neil Oct 12 '15 at 03:05

3 Answers3

16

This java class implements a TextWatcher to "watch" your edit text, watching any changes done to the text:

public abstract class TextValidator implements TextWatcher {
    private final TextView textView;

    public TextValidator(TextView textView) {
        this.textView = textView;
    }

    public abstract void validate(TextView textView, String text);

    @Override
    final public void afterTextChanged(Editable s) {
        String text = textView.getText().toString();
        validate(textView, text);
    }

    @Override
    final public void 
    beforeTextChanged(CharSequence s, int start, int count, int after) {
         /* Needs to be implemented, but we are not using it. */ 
    }

    @Override
    final public void 
    onTextChanged(CharSequence s, int start, int before, int count) { 
         /* Needs to be implemented, but we are not using it. */    
    }
}

And in your EditText, you can set that text watcher to its listener

editText.addTextChangedListener(new TextValidator(editText) {
    @Override public void validate(TextView textView, String text) {
       /* Insert your validation rules here */
    }
});
ZooMagic
  • 616
  • 7
  • 15
Randyka Yudhistira
  • 3,612
  • 1
  • 26
  • 41
  • 1
    Is it a good pattern to pass instance of UI components to classes that should deal with only logic? I think better way would be to get validate response from validator class and in the implementation class use the value to manipulate the UI. – Ashish Dulhani Nov 21 '18 at 15:15
7

One approach (which I am using) is you should have a helper for validating inputs such as:

  1. Nullity (or emptiness)
  2. Dates
  3. Passwords
  4. Emails
  5. Numerical values
  6. and others

here's an exerpt from my ValidationHelper class:

public class InputValidatorHelper {
    public boolean isValidEmail(String string){
        final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
        Pattern pattern = Pattern.compile(EMAIL_PATTERN);
        Matcher matcher = pattern.matcher(string);
        return matcher.matches();
    }

    public boolean isValidPassword(String string, boolean allowSpecialChars){
        String PATTERN;
        if(allowSpecialChars){
            //PATTERN = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})";
            PATTERN = "^[a-zA-Z@#$%]\\w{5,19}$";
        }else{
            //PATTERN = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})";
            PATTERN = "^[a-zA-Z]\\w{5,19}$";
        }



        Pattern pattern = Pattern.compile(PATTERN);
        Matcher matcher = pattern.matcher(string);
        return matcher.matches();
    }

    public boolean isNullOrEmpty(String string){
        return TextUtils.isEmpty(string);
    }

    public boolean isNumeric(String string){
        return TextUtils.isDigitsOnly(string);
    }

    //Add more validators here if necessary
}

Now the way I use this class is this:

InputValidatorHelper inputValidatorHelper = new InputValidatorHelper();
StringBuilder errMsg = new StringBuilder("Unable to save. Please fix the following errors and try again.\n");
//Validate and Save
boolean allowSave = true;
if (user.getEmail() == null && !inputValidatorHelper.isValidEmail(user_email)) {
    errMsg.append("- Invalid email address.\n");
    allowSave = false;
}

if (inputValidatorHelper.isNullOrEmpty(user_first_name)) {
    errMsg.append("- First name should not be empty.\n");
    allowSave = false;
}

if(allowSave){
    //Proceed with your save logic here
}

You can call your validation by using TextWatcher which is attached via EditText#addTextChangedListener

example:

txtName.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        //Do nothing
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        validate();
    }
});
PinoyCoder
  • 1,112
  • 8
  • 13
  • There are Android special InputTypes for all the TextView types you've listed. Why do you need validation? – Zon Jul 04 '17 at 07:30
0

I think in order to handle it in a clean way, each view should keep their validation logic and their valid status. We shouldn't use same code lines in each screen.

For ex: if you have a phone number field, you shouldn't write its validation logic in every screen. You can create a custom view for phone field and keep its validation logic inside that custom view. By this you will get rid of field validation in each screen But it is not finished yet, you can also handle on valid state inside your custom button view. By this you will eliminate duplicate lines of valid state handling from your screens. And at last you need to just a logic to introduce input fields to your button

I have extracted all these logic as a lib and added simple demo for its usage.

To use it you need to just implement an interface in your custom input views and another interface in your custom button. Then you can bind all inputs to your button with one line of code in each screen

Demo.gif

Usage

For Input Field: You need to implement Validatable interface in your input field. 2 methods will be implemented in here. In this example our custom input view extends AppCompatEditText

/**
 * Validity State of View
 * In here input field is valid if any text is entered
 * */
override val isValid: Boolean
    get() = length() > 0

/**
 * Informing Validator about change in input field
 * In here we are rechecking validity every time on after text change
 * */
override fun setupValidationListener(validator: Validator?) {
    addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

        /**
         * To inform Validator view is changed
         * */
        override fun afterTextChanged(p0: Editable?) {
            validator?.checkAll()
        }
    })
}


For Output view(Button in most cases): You need to implement Verifier interface which handles onValid state of button. This method will be invoked if every input in page is valid.
/**
 *  Enabling button if validator informs that all inputs are valid
 */
override fun onValid(isValid: Boolean) {
    isEnabled = isValid
}


Binding input and output views: You have add this line to your activity/fragment for validation binding. First variable is your Verifier view then you can add all of your Validatable views, there is no Validatable view limit
ValidationBinder.bind(bindind.btnContinue, bindind.inputFirst, bindind.inputSecond)