InputFilter for two capital letters and one or two digits, works only if the first input is a digit

208 Views Asked by At

I need to restrict the input that can be entered into a TextInputEditText to two capital letters and one or two digits, as in AB01, CS8, XY99 or ND5.

So I wrote this method:

/**
 * @return an InputFilter that restricts input to the format of two capital letters and one or two digits
 */
public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        if (input.matches("^[A-Z]{0,2}[0-9]{0,2}$")) {
            if (input.length() == 1 && input.matches("^[0-9]$")) {
                Log.v(DEBUG_TAG, "Invalid input A");
                return "";
            }
            if (input.length() == 2 && !input.matches("^[A-Z]{2}$")) {
                Log.v(DEBUG_TAG, "Invalid input B");
                return "";
            }
            Log.v(DEBUG_TAG, "Valid input");
            return null;
        }
        Log.v(DEBUG_TAG, "Invalid input C");
        return "";
    };
}

That I use like this:

TextInputEditText clientId = dialogView.findViewById(R.id.edit_text_client_id);
InputFilter[] clientIdFilters = new InputFilter[1];
clientIdFilters[0] = InputFilterUtils.getClientIdInputFilter();
clientId.setFilters(clientIdFilters);

It almost works perfectly...

The problem is that only when starting typing from an empty textfield, after the first two capital letters (so at the 3rd one) the field gets deleted completely. If, instead, a number is entered first (and gets correctly ignored), if you continue to type letters, when reaching the 3rd letter, this time the first two remains and the user can continue entering the digits...

I don't have a clue for this behavior (I added some logging entries, but it's not clear anyway)

3

There are 3 best solutions below

1
xenon134 On

Why are you using regex for this? This is a simple validation logic.

public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        
        // changes start from here
        if (input.length() != 3 && input.length() != 4)
            return "";
        int numberOfCapitalLetters = 0, nuberOfDigits = 0;
        for (char i: input.toCharArray()) { // loop over all the letters
            if (i >= 'A' && i <= 'Z') // capital letter
                numberOfCapitalLetters += 1;
            else if (i >= '0' && i <= '9')
                nuberOfDigits += 1;
            else 
                return ""; // only capital letters and numbers allowed
        }
        if ((numberOfCapitalLetters == 2) && ((nuberOfDigits == 1) || (nuberOfDigits == 2)))
            return null; // valid
        else
            return "";
    };
}
7
ndriqa On

As I can understand @xenon134's answer, it doesn't actually check if the first and the second characters are letters, so, since the the input should be limited to 3 or 4 characters, I would go to a more hardcoded solution and not overengineer it, so:

public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        
        // changes start from here
        if (input.length() == 1 && (input.charAt(0) < 'A' || input.charAt(0) > 'Z'))
            return ""
        if (input.length() == 2 && (input.charAt(1) < 'A' || input.charAt(1) > 'Z'))
            return ""
        if(input.length() == 3 && (input.charAt(2) < '0' || input.charAt(2) > '9')) 
            return "";
        if(input.length() == 4) {
            char ch4 = input.charAt(3);
            if(ch4 < '0' || ch4 > '9') 
                return "";
        }
        if (input.length() > 4)
            return "";
        
        return null; //valid
    };
}

*pardon me if I have forgotten any semicolon as I'm working with Kotlin for a while now, also not sure if I can get the char by index like that in Java, you might need to use input.charAt(index)

Additionally, you can extract some code to check whether a char is a letter or a number on separate functions so the code looks more readable:

  • public static boolean isLetter(char c) { ... }
  • public static boolean isNumber(char c) { ... }
1
Sudhakar kr On

If you want use regex method use below.

Pattern pattern = Pattern.compile("[A-Z]{2}\\d{1,2}");
    
String input = dest.subSequence(0, dstart)
             + source.toString()
             + dest.subSequence(dend, dest.length());

Matcher matcher = pattern.matcher(input);
if (!matcher.matches()) {
    return "";
}

return null;