Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android TextWatcher Firing

I have an Android programming question. Using the code below I want to validate a string match. It validates fine but LogCat is showing that the TextWatcher methods are firing twice per a keystroke and I can't figure out why. I would like for the firing to only occur once per keystroke.

Do you know why it's doing this?

I thought it might be because I change the color of the text but after commenting it out it didn't make a difference.

LogCat Output

03-31 03:37:25.269: I/BeforeText(676): Hit 
03-31 03:37:25.269: I/OnText(676): Hit
03-31 03:37:25.269: I/AfterText(676): Hit
03-31 03:37:25.274: I/InvalidText(676): Incorrect Text.
03-31 03:37:25.274: I/Text Value(676): a
03-31 03:37:25.404: I/BeforeText(676): Hit
03-31 03:37:25.404: I/OnText(676): Hit
03-31 03:37:25.404: I/AfterText(676): Hit
03-31 03:37:25.404: I/InvalidText(676): Incorrect Text.
03-31 03:37:25.404: I/Text Value(676): a

Activity Code

public void onCreate(Bundle savedInstanceState) {

     //...omitted

    //Create Answer Field
    textField = (EditText)this.findViewById(R.id.textField);

    //Add validation to TextField
    textField.addTextChangedListener(new TextWatcher(){
        public void afterTextChanged(Editable s){

            Log.i("AfterText","Hit");

            if(textField.getText().toString().trim().equalsIgnoreCase("hello")){
                Log.i("ValidText", "Text matched.");

                answerField.setTextColor(Color.GREEN);

            }
            else{
                Log.i("InvalidText", "Incorrect text.");
                Log.i("Text Value", textField.getText().toString());

                textField.setTextColor(Color.RED);

            }
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after){
            //Do nothing
            Log.i("BeforeText", "Hit");
        }

        public void onTextChanged(CharSequence s, int start, int before, int count){
            //Do nothing
            Log.i("OnText","Hit");

        }
    });
}
like image 660
AmarettoSlim Avatar asked Aug 01 '26 16:08

AmarettoSlim


2 Answers

As your Question is for TextWatcher methods are firing twice per a keystroke. You have use TextWather for Make watch on EditText for Validate String and set Color .

You can refer Document for TextWatcher in developer site here. http://developer.android.com/reference/android/text/TextWatcher.html.

As when you make press keystore it will make change in EditText text that way TextWatcher method onTextChanged call ,when you press any key for EditText method beforeTextChanged this will call when we start edit EditText.

One More thing that is when you enter one character in EditText ,it will call all this three method of Textwather.Just there sequence for Call are different.and also refre this SO Question Android TextWatcher.afterTextChanged vs TextWatcher.onTextChanged

So there is nothing wrong will call twice for Text Change in EditText.

Hope you get understand.

like image 121
Herry Avatar answered Aug 03 '26 07:08

Herry


I don't think if this would help you but in my case it seems to be the swipe keyboard.

The point is, when the swipe tries to make a suggestion the onchange methods seems to be called twice (one from EditText and another from swipe keyboard).

I don't know if this is feasible in your code, but just try this on your EditText to see what happen:

android:inputType="textNoSuggestions"

You can check the next site talking about this issue:

http://support.swiftkey.net/forums/116693-2-bug-reports/suggestions/2994580-span-exclusive-exclusive-spans-cannot-have-a-zero-

I will edit this answer if I found out a solution.


EDIT


My conclusion is that I can't avoid the intermediate events and I can't make the difference between a real change event from the user or a swipe change event.

Depending on your real problem you should try a different workaround. One workaround could be to save the information updated in a ConcurrentHashMap and schedule a task 100 milliseconds after the event is received. At the same time you should setup a "lastModified" date.

In the scheduled task you should ask "has elapsed 100 millisecond from last update?", if the answer is no --> do nothing (this means that another event has arrived so another updating task will be executed with the last value).

Acompany all with a ReentrantLock to reach atomicity.

Something like this:

   private final ReentrantLock lock = new ReentrantLock();
    private final ScheduledExecutorService scheduleExecutor = new ScheduledThreadPoolExecutor(1);
    private Date lastModification = null;
    private static final long UPDATE_WINDOW_IN_MILLISECONDS = 100;

protected class UpdateBusinessLogicTask implements Runnable {
    private final String newVal;

    public UpdateBusinessLogicTask(String newVal) {
        super();
        this.newVal = newVal;
    }

    @Override
    public void run() {

        //check current date
        Date now = new Date();
        //get lock to achieve atomicity 
        lock.lock();

        // calculate elapsed time
        long elapsedTime = lastModification.getTime() - now.getTime();

        // free the lock --> you could free the lock after business logic, depends on your scenario
        lock.unlock();

        //if the time is less than the updating window (100 milliseconds)
        if (elapsedTime < (UPDATE_WINDOW_IN_MILLISECONDS - 1)) {
            // DO NOTHING!!
            Log.d("TEST", "Dismiss update "+newVal);
        } else {
            Log.d("TEST", "Updating business with newVal: "+newVal);
            // TODO implement business logic
        }

    }
};

protected void onCreate(Bundle savedInstanceState) {

    EditText view = findViewById(R.id.nameEditText);

    TextWatcher tw = new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            Log.d("TEST", "New text event received, new val: " + s.toString());

            lock.lock();
            lastModification = new Date();

            scheduleExecutor.schedule(new UpdateBusinessLogicTask(s.toString()), UPDATE_WINDOW_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
            lock.unlock();
        }

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

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

}
like image 43
Carlos Verdes Avatar answered Aug 03 '26 05:08

Carlos Verdes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!