Jhonycage
Jhonycage

Reputation: 859

TextWatcher, problems clearing Edit Text

I am trying to limit an edit text of one character, I have a CustomTextWatcher class which handle all text changes events. Lets say if i write one letter, the focus will automatically jump to the next edit text, but if I touch on the editText to getFocus if cursor is before previous letter it allows me to write one letter being 2 then, i tried clearing edit text in beforeTextChanged but it crashes the app, any ideas of how to approach this problem?

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    EditText e = (EditText) v;
    if(e.getText().toString().length() == 0 || e.getText().toString().equals(""))
        e.setText(s); // if true means there is no previous text so i can just use whatever comes in s charSequence
    else
        e.setText(""); // if false, means there is some text, so i want to clean it up first and write new character
    Log.d(TAG, "beforeTextChanged: ");
}

UPDATE : ok so after following the recomendations this is what I've got:

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    hasToClear = false;
    e = (EditText) v;
    if(e.getText().toString().length() > 0 || !e.getText().toString().equals("")) {
        previousChar = e.getText().toString(); // if either true, the box was not empty
        hasToClear = true;
    }
    Log.d(TAG, "beforeTextChanged: ");
}


@Override
public void afterTextChanged(Editable s) {

    InputFilter[] editFilters = s.getFilters();
    InputFilter[] newFilters = new InputFilter[editFilters.length + 2];
    System.arraycopy(editFilters, 0, newFilters, 0, editFilters.length);
    newFilters[editFilters.length] = new InputFilter.AllCaps();
    if(!isNumber)
        newFilters[editFilters.length+1] = charFilter;
    else
        newFilters[editFilters.length + 1] = digitFilter;
    s.setFilters(newFilters);
    if(isAllowed) {
        if (i == NUM_DIGITS) {
            edList[5].clearFocus();
            onTextFinishedListener.onPlateFinished(true);
        } else {
            edList[i].selectAll();
            edList[i].requestFocus();
        }
    }
    String stringToReplace = "";
    if(previousChar != null) { // find which caracter is diferent as there is only one allowed
        for (int x = 0; x < s.length(); x++) {
            if (s.charAt(x) != previousChar.charAt(0)) {
                stringToReplace = String.valueOf(s.charAt(x));
                break;
            } else stringToReplace = String.valueOf(previousChar);
        }
    }
    if(hasToClear){ // if true clear
        e.removeTextChangedListener(this);
        e.setText("");
        e.setText(stringToReplace);
        e.addTextChangedListener(this);
    }
}

if user clicks on, before or after any character the next input user does, must replace to previous character, this is happening with before and on character, but is not happening with after

Upvotes: 0

Views: 2460

Answers (3)

Jhonycage
Jhonycage

Reputation: 859

After dealing with different approaches, I decided to walk away from input filters, lets say they did not behave in the way I wish they would have, so in order to not break the UI by just blocking Characters from the xml, I use two methods:

private boolean validateLetter(Character s){
    if(s == null)
        return false;
    if(!Character.isLetter(s)) { //if it isn't a letter, throw message
        Toast.makeText(mContext, "Character not allowed", Toast.LENGTH_SHORT).show();
        return false;
    } else {
        return true;
    }
}

private boolean validateNumber(Character s){
    if(s == null)
        return false;
    if(!Character.isDigit(s)) { // if it isn't a digit, throw message-
        Toast.makeText(mContext, "Character not allowed", Toast.LENGTH_SHORT).show();
        return false;
    } else {
        return true;
    }
}

and the TextWatcher looks as it follows:

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    hasToClear = false;
    isAllowed = false;
    e = (EditText) v;
    if(e.getText().toString().length() > 0 || !e.getText().toString().equals("")) {
        previousChar = e.getText().toString(); //store previous char if there is any
        hasToClear = true; // rise flag for clearing field in after event
    }
    Log.d(TAG, "beforeTextChanged: ");
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    isNumber = false;
    String[] data= v.getTag().toString().split(";"); //recieve the type of input for the edit text and its position in the layout by xml tag
    String type = data[0];
    pos = Integer.parseInt(data[1]);
    if(type.equals("number"))
        isNumber = true;
}

@Override
public void afterTextChanged(Editable s) {

    Character stringToReplace = null;
    if(previousChar != null) { // 
        for (int x = 0; x < s.length(); x++) {
            if (s.charAt(x) != previousChar.charAt(0)) { //this bucle find the different character in the edit text
                stringToReplace = s.charAt(x);
                break;
            } else stringToReplace = previousChar.charAt(0);
        }
    }
    if(isNumber){
        if(validateNumber(stringToReplace)){
            isAllowed = true;
        }else isAllowed = false;
    }else
        if(validateLetter(stringToReplace)){
            isAllowed = true;
        } else isAllowed = false;

    if(isAllowed) {
        if(hasToClear){
            e.removeTextChangedListener(this); //remove listener
            e.setText(""); //clear field
            e.setText(String.valueOf(stringToReplace)); //add new value
            e.addTextChangedListener(this); //add listener back
        }
        if (pos == NUM_DIGITS) { //jump to next editText until it reaches NUM_DIGITS, after will notify that it has finished filling all editText
            edList[5].clearFocus();
            onTextFinishedListener.onPlateFinished(true);
        } else {
            edList[pos].selectAll();
            edList[pos].requestFocus();
        }
    } else {
        if(previousChar != null) {
            e.removeTextChangedListener(this);
            e.setText(String.valueOf(previousChar));
            e.addTextChangedListener(this);
        }
    }
    isAllowed = false;
}

Special thanks to

@Veneet Reddy

Upvotes: 0

Lalit Fauzdar
Lalit Fauzdar

Reputation: 6363

Why are you using that code, you can use this.

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    EditText e = (EditText) v;
    if(e == null)
        e.setText(s); 
    else
        e.setText(null); 
    Log.d(TAG, "beforeTextChanged: ");
}

Also, you can set an onClickListener to the edittext. This will make the edittext null if it's not empty and clicked again.

e.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View arg0) {

               if(e != null){
                 e.setText(null);
                }
              else
               {}
            }
        });

You can also call your beforeTextChanged method in blank else of onClick.

Upvotes: 0

Veneet Reddy
Veneet Reddy

Reputation: 2927

From the docs of beforeTextChanged:

This method is called to notify you that, within s, the count characters beginning at start are about to be replaced by new text with length after. It is an error to attempt to make changes to s from this callback.

Hence you should move the same code to afterTextChanged where it is legal to make changes to the EditText.

Note: If you are setting text in afterTextChanged on the same EditText then the TextWatcher will be fired. If this is not intended behaviour, should remove the TextWatcher, set the text and then add the TextWatcher back.

Upvotes: 1

Related Questions