Hot questions for Using Android EditText in textwatcher

Question:

I have a EditText with TextWatcher.


Scenario 1:

EditText containing "abcd"

If i press return key or enter newline

1) before the characters, TextWatcher fires 3 times.

2) in between the characters, TextWatcher fires 4 times.

3) at the end of the characters, TextWatcher fires 1 time.


Scenario 2:

EditText containing "1234"

If i press return key or enter newline

1) before the characters, TextWatcher fires 1 times.

2) in between the characters, TextWatcher fires 1 times.

3) at the end of the characters, TextWatcher fires 1 time.


Is this a bug?

Or is there any thing what i do not understand?


I want the text watcher to fire only once for all the scenario.

Any help will be highly appreciated.


Answer:

I found the solution but may not be the perfect for all needs.

Earlier, when the TextWatcher was firing multiple times and code in it was also executed multiple times, I was with

editText.addTextChangedListener(new TextWatcher() {

    public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

        Log.e(TAG, "111 text =---------------" + charSequence);
    }

    public void onTextChanged(CharSequence charSequence, int start, int before, int count){

        Log.e(TAG, "222 text =---------------" + charSequence);
    }

    public void afterTextChanged(Editable editable) {

        Log.e(TAG, "333 text ---------------" + editable);
    }
});

Now, as per my requirements I found the solution and I am with

editText.addTextChangedListener(new TextWatcher() {

    String initialText = "";
    private boolean ignore = true;

    public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

        if ( initialText.length() < charSequence.length() ){

            initialText = charSequence.toString();
            Log.e(TAG, "111 text ---------------" + charSequence);
        }
    }

    public void onTextChanged(CharSequence charSequence, int start, int before, int count){

        if( initialText.length() < charSequence.length() ) {

            initialText="";
            ignore=false;
            Log.e(TAG, "222 text ---------------" + charSequence);
        }
    }

    public void afterTextChanged(Editable editable) {

        if(!ignore) {

            ignore = true;
            Log.e(TAG, "333 text ---------------" + editable);
        }
    }
});

Now also the TextWatcher is firing multiple times but the code in the if conditions are executed only once for all the scenario that i mentioned in my question.

Question:

I have a TextWatcher on my android app. Problem is I want it to listen on every text change, and append a string of the same TextView.

For example, a user inputs A1A2A3A4A5A6A7A8A9 on the EditText field, then I want a textview right on top to hyphenate after 6 characters. so the text view dispalys A1A2A3-A4A5A6-A7A8A9, while the edit text still diplays A1A2A3A4A5A6A7A8A9

I tried this on aftertextchanged, and it did not work:

    @Override
public void afterTextChanged(Editable s) {

    String hyphenated = "";


    if (s.length() == 6)
    {
        hyphenated = s.toString()+"-";
        mCounter.setText(hyphenated);
    }

    if (s.length() == 12)
    {
        hyphenated = s.toString()+"-";
        mCounter.setText(hyphenated);
    }

}

Answer:

For example:

    String myString = "A1A2A3A4A5A6A7A8A9";
    StringBuilder str = new StringBuilder(myString);
    for(int i = 0; i < str.length(); i++){
        if(i == 6){
            str.insert(i, "-");
        }
        if(i == 13){
            str.insert(i, "-");
        }
    }
    System.out.println(str.toString());

Output would be: A1A2A3-A4A5A6-A7A8A9

Question:

I am trying to disable my button if my input edit texts are empty. I am using text watcher for this. To test it out , i have only tried with only two edit texts to start. However, my button stays enabled no matter what.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_create_profile);

    fnameInput = findViewById(R.id.et_firstname);
    lnameInput = findViewById(R.id.et_lastname);
    numberInput = findViewById(R.id.et_phone);
    emailInput = findViewById(R.id.et_email);
    nextBtn = findViewById(R.id.btn_next);

    fnameInput.addTextChangedListener(loginTextWatcher);
    lnameInput.addTextChangedListener(loginTextWatcher);


    nextBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            launchNextActivity();
        }
    });
}

Text watcher method

private TextWatcher loginTextWatcher = new TextWatcher() {

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

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

        String firstNameInput =firstNameInput.getText().toString().trim();
        String lastNameInput = lastNameInput.getText().toString().trim();


        // tried doing it this way 
        nextBtn.setEnabled(!firstNameInput.isEmpty() && !lastNameInput.isEmpty());

        //What i've also tried 
        if(firstNameInput.length()> 0 &&
                lastNameInput.length()>0){
            nextBtn.setEnabled(true);
        } else{
            nextBtn.setEnabled(false);
        }
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
};

I expect the button to be disabled if one or all inputs are empty and enabled when all input fields are filled out.


Answer:

create a method check all condition there like

private void checkTheConditions(){
  if(condition1 && condition2)
  nextBtn.setEnabled(true)
  else
  nextBtn.setEnabled(false)
}

call this method from afterTextChanged(Editable s) method

Question:

I have a "current password" EditText and an error TextView which displays an error message. I want to clear the error message when the EditText contents change, usually when the user types in another letter or erases something.

The error TextView is not being cleared even though the system prints the messages. The TextView is only being cleared when the EditText loses focus.

Why is this happening? Are these methods being executed on a different thread? How should I clear the TextView immediately after the user types in a character?

    mCurrentPasswordEditText = (EditText) view.findViewById(R.id.current_password_edit_text);
    mCurrentPasswordErrorTextView = (TextView) view.findViewById(R.id.current_password_error_text_view);
    mCurrentPasswordEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            //mCurrentPasswordErrorTextView.setText("");
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            //mCurrentPasswordErrorTextView.setText("");
        }
        @Override
        public void afterTextChanged(Editable s) {
            System.out.println("called multiple times.");
            mCurrentPasswordErrorTextView.setText("");
        }
    });

Answer:

The solution is very weird. I think this is a bug!

Instead of replacing the TextView with an empty string "", I put an extra space " ", which cleared the text immediately! *smh

mCurrentPasswordEditText = (EditText) view.findViewById(R.id.current_password_edit_text);
mCurrentPasswordErrorTextView = (TextView) view.findViewById(R.id.current_password_error_text_view);
mCurrentPasswordEditText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        //mCurrentPasswordErrorTextView.setText("");
    }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //mCurrentPasswordErrorTextView.setText("");
    }
    @Override
    public void afterTextChanged(Editable s) {
        mCurrentPasswordErrorTextView.setText(" ");
    }
});

Question:

I'm trying to implement a coding IDE in an Android App. I created a Multi Line EditText to write code in. To change colors of keywords, I replace the text in TextWatcher's afterTextChanged() method. The problem is that on typing enter, the cursor does not move to the next line. If I remove the code that's below, everything works fine (typing and moving to new lines).

@Override
public void afterTextChanged(Editable s) {
    String replaceText = codeEditText.getText().toString();
    // Some logic that changes contents of replaceText
    codeEditText.removeTextChangedListener(this);
    codeEditText.setText(Html.fromHtml(replaceText));
    codeEditText.setSelection(codeEditText.length(), codeEditText.length());
    codeEditText.addTextChangedListener(this);
}

I've also tried using s.replace(0, s.length(), Html.fromHtml(replaceText));, but it doesn't work either. Is there a better way to change the value of EditText from within the TextWatcher, besides the two above (detaching-reattaching, s.replace).


Answer:

If anyone has similar issues, or is interested -

I figured out the problem, it wasn't with the listener but more so with the HTML parsing portion. Html.fromHtml() has trouble with \n characters. Even after replacing all the \n's with <br> tags, the error persisted. After moving to an approach where I used SpannableStringBuilder to change keyword colors, everything fell into place.

@Override
public void afterTextChanged(Editable s) {
    SpannableStringBuilder ssb = new SpannableStringBuilder(s.toString());

    for (int i = 0; i<keyWords.size(); i++){
        String keyword = keyWords.get(i);
        Pattern pattern = Pattern.compile("\\b"+keyword+"\\b");
        Matcher matcher = pattern.matcher(ssb);
        while(matcher.find()){
            // Have to create a new instance of FgColor for this to work!!
            // KeywordColors is a Hashmap mapping keywords to the color they should be highlighted with
            ForegroundColorSpan fg = new ForegroundColorSpan(keywordColors.get(keyword).getForegroundColor());
            ssb.setSpan(fg, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    codeEditText.removeTextChangedListener(this);
    codeEditText.setText(ssb);
    codeEditText.addTextChangedListener(this);

    codeEditText.setSelection(codeEditText.getText().length());

}

Another thing I noticed was when I used one FGspan for multiple words, only the latest word it was applied to actually got colored. So to solve this, create new instances of FGspan for each new word to highlight.

Question:

So, I want a user to enter his/her number plate of his/her car and I want it to be quite stylish by separating all the characters like this picture: enter image description here

Right now it consists of 6 EditTexts with TextWatchers that change the focus for each input. That part works fine but my problem is with the deletion och characters. When a user want to edit one field, he/she can just click the view and delete eand replace. Although when the whole thing is wrong it is not possible to delete everything at once but you have to click every view and delete by hand.

So what I need is so when the user hit backspace on an empty view, it should change focus to the one before and delete that character and so on. So all the EditTexts will be connected and work as one. I've tried with KeyListeners that listens to backspace but that only works with hardware keyboards and not soft keyboards on the phone.

I'm also happy if someone can point me in the direction of another solution better than this one.

TextWatcher:

registrationPlateEditTexts is an List of all the EditText in order.

private TextWatcher regPlateTextWatcher = new TextWatcher() {
        @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
        @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
            for (int i=registrationPlateEditTexts.size()-1; i>=0; i--){
                if (registrationPlateEditTexts.get(i).getText().length()==1 && i!=registrationPlateEditTexts.size()-1){
                    registrationPlateEditTexts.get(i+1).requestFocus();
                    return;
                }
                else if (registrationPlateEditTexts.get(i).getText().length()==1){
                    InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }
            }
        }
        @Override public void afterTextChanged(Editable s) {}
    };

            else if (registrationPlateEditTexts.get(i).getText().length()==1){
                InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
            }
        }
    }
    @Override public void afterTextChanged(Editable s) {}
};`

Answer:

I took 4 custom edit text with property of getting selected when you press back. Here are the listeners and the CustomEditText elements

mCodeFourEt.setOnEditorActionListener(new EditText.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                mConfirmBtn.performClick();
                return true;
            }
            return false;
        }


    });


    mCodeTwoEt.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DEL) {
                String text = mCodeTwoEt.getText().toString();
                if (text.length() == 0) {
                    mCodeOneEt.requestFocus();
                    mCodeOneEt.selectAll();
                    return true;
                }
            }

            return false;
        }
    });

    mCodeThreeEt.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DEL) {
                String text = mCodeThreeEt.getText().toString();
                if (text.length() == 0) {
                    mCodeTwoEt.requestFocus();
                    mCodeTwoEt.selectAll();
                    return true;
                }
            }

            return false;
        }
    });

    mCodeFourEt.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DEL) {
                String text = mCodeFourEt.getText().toString();
                if (text.length() == 0) {
                    mCodeThreeEt.requestFocus();
                    mCodeThreeEt.selectAll();
                    return true;
                }

            }

            return false;
        }
    });

    mCodeOneEt.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable editable) {
            if (mCodeOneEt.getText().toString().length() > 0) {
                mCodeTwoEt.requestFocus();
            }

        }
    });

    mCodeTwoEt.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable editable) {
            if (mCodeTwoEt.getText().toString().length() > 0) {
                mCodeThreeEt.requestFocus();
            }

        }
    });

    mCodeThreeEt.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable editable) {
            if (mCodeThreeEt.getText().toString().length() > 0) {
                mCodeFourEt.requestFocus();
            }

        }
    });

    mCodeFourEt.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable editable) {

        }
    });

Now here is the custom edittext class

public class CustomEditText extends android.support.v7.widget.AppCompatEditText {


public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public CustomEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomEditText(Context context) {
    super(context);
}


@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
            true);
}

private class ZanyInputConnection extends InputConnectionWrapper {

    public ZanyInputConnection(InputConnection target, boolean mutable) {
        super(target, mutable);
    }

    @Override
    public boolean sendKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN
                && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
            // Un-comment if you wish to cancel the backspace:
            // return false;
        }
        return super.sendKeyEvent(event);
    }


    @Override
    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
        // magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace
        if (beforeLength == 1 && afterLength == 0) {
            // backspace
            return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                    && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
        }

        return super.deleteSurroundingText(beforeLength, afterLength);
    }

}
}

Just use this custom class in your XML and see the example above which is for 4 edit text.

Question:

For example: if I typed "hello sir", then how can I get "hello" as soon as I tapped space bar before I start typing "sir" using textwatcher in android. I need currently typed single text from edittext as soon as user hit the space bar not all the string from edittext. Can anyone provide reliable idea using textwatcher?

public void onTextChanged(CharSequence charsequence, int i, int j, int k) {
                // TODO Auto-generated method stub
                // I need only currently typed text not all the string from edittext
            }
        });

Answer:

You get the entire string whenver onTextChanged is called, there is no "when spacebar is pressed" callback, so you'll have to write your own.

An idea could be the following: In onTextChanged(), check the string for any spaces, and if there is any found, go through the string, split the string into an array of space seperated strings. Then you have each string in an array, and then simply find the one you need.

This would turn the string "How are you doing today?" into an array of "How" "are" "you" "doing" "today?", and then its a simple case of array indexing to find the string you need.