AsafK
AsafK

Reputation: 2485

TextWatcher events are being fired multiple times

I have an annoying problem with TextWatcher. i've been searching the web but couldnt find anything. appreciate if someone could assist me.

For some reason the calls to the TextWatcher events upon one text change are erratic. sometimes they are being triggered once (like they should be), sometimes twice, and sometimes 3 times. have no idea why, the whole thing is very straight forward. also sometimes the Editable parameter on afterTextChanged() returns empty values in toString() and length().

code is below:

    private TextWatcher mSearchAddressTextChangeListener = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) { }

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

        @Override
        public void afterTextChanged(Editable searchedAddress) {
           System.out.println("called multiple times.");   
        }
    };

inside afterTextChanged() (and the AsyncTask) im not making any change to the text or the EditText view.

i saw the question asked in Events of TextWatcher are being called twice, but im having the events triggered more (or less) than twice.

anyway, appreciate any help.

EDIT: I removed the content of afterTextChanged() cause this problem is happening even without my code. what leads me to believe this is a bug. The bug is occuring when a 'space' char is entered right after a regular char (event handlers are triggered twice) or when a 'space' char after a regular char is removed (backspace. event handlers are triggered 3 times). help will still be appreciated.

Upvotes: 56

Views: 29814

Answers (12)

ubzack
ubzack

Reputation: 1876

Just keep in mind any time EditText.setText() is called the TextWatcher overrides will be called.

Upvotes: 0

himanshu patel
himanshu patel

Reputation: 1

this problem is happening because of edittext focus whenever if you are using textChangeListiner always add yourSearchview/edittext.isFocused like this searchView.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged( s: CharSequence?, start: Int, count: Int, after: Int ) {

                }

                override fun onTextChanged(
                    s: CharSequence?,
                    start: Int,
                    before: Int,
                    count: Int
                ) {

                }
                override fun afterTextChanged(s: Editable?) {
                   if(searchView.isFocused){
                      
                   }
                }
            })
by using this when edittext is focus then it will do your work

Upvotes: 0

mohamed murashid
mohamed murashid

Reputation: 719

Check the code properly, you have added TextWatcher mulitple times.

Upvotes: 0

Erkan
Erkan

Reputation: 165

alternatively, instead of TextWatcher you can use

editTxtView.doOnTextChanged { text, start, before, count ->  }
editTxtView.doAfterTextChanged {  }

Upvotes: 0

Kunal Pasricha
Kunal Pasricha

Reputation: 11

Let's assume the typing speed per character is 300ms.

    private var textDispatcherHandler = Handler(Looper.getMainLooper())
    private var textDispatcherRunnable:Runnable? = null

    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        if(p0!=null){
            dispatchTextToFilter(p0.toString())
        }
    }
    
    private fun dispatchTextToFilter(string: String){
        if(textDispatcherRunnable!=null){
            textDispatcherHandler.removeCallbacks(textDispatcherRunnable!!)
        }
    
        if(textDispatcherRunnable==null){
            textDispatcherRunnable = Runnable {
                //doSomething here
            }
        }
        textDispatcherHandler.postDelayed(textDispatcherRunnable!!,300)
    }

Upvotes: 0

Guimo
Guimo

Reputation: 345

My issue was related to this as I was running my app in an emulator and typing away through the laptop's keyboard (not using the emulator's one) and the odd input would be duplicated.

This didn't happen in a physical device.

Following some inspiration from Nico's answer above, this is what worked for me to have the input text changed being triggered only once.

Added the following input type config, either:

1) Through a style

<!-- in a style -->
<style name="My.Text.Style">
 <item name="android:inputType">text|textMultiLine|textVisiblePassword</item>
</style>

and then used in xml as:

<EditText
   android:id="@+id/my_edit_text"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   style="@style/My.Text.Style"
   />

OR

2) Straight into the xml

<EditText
   android:id="@+id/my_edit_text"
   android:inputType="text|textMultiLine|textVisiblePassword"
   />

Both ways of doing it should be fine, the style was just for the case one wants to reuse it across other screens.

Upvotes: 0

shankar_vl
shankar_vl

Reputation: 82

For me the below code worked. Please observe that boolean value has been changed after if condition loop in afterTextChanged method

        edittext.addTextChangedListener(new TextWatcher() 
        {
            boolean considerChange = false;

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

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

            public void afterTextChanged(Editable editable) 
            {
                if (considerChange) 
                { 
                    // your code here
                }
                considerChange = !considerChange; //see that boolean value is being changed after if loop
            }


        });

Upvotes: 2

&#194;ngelo Polotto
&#194;ngelo Polotto

Reputation: 9511

I tried all the solution answered on this question, none of those worked for me. But after some search I found this post. Using the RxJava to make the debounce worked well for me. Here goes my final solution:

Add RxJava dependencies in the Gradle file:

compile 'io.reactivex:rxandroid:1.0.1'
compile 'io.reactivex:rxjava:1.0.14'
compile 'com.artemzin.rxjava:proguard-rules:1.0.14.2'

Implement your subject:

PublishSubject<String> yourSubject = PublishSubject.create();
    yourSubject .debounce(100, TimeUnit.MILLISECONDS)
            .onBackpressureLatest()
            .subscribe(s -> {
                //Implements anything you want
            });

Use your subject in your TextWatcher:

TextWatcher myTextWatcher = 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) {
        yourSubject.onNext(s.toString()); //apply here your subject
    }

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

Add the TextWatcher in the EditText listener:

my_edit_text.addTextChangedListener(myTextWatcher);

Upvotes: 4

velraj
velraj

Reputation: 261

boolean isOnTextChanged = false;

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

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    isOnTextChanged = true;
}

@Override
public void afterTextChanged(Editable quantity) {
    if (isOnTextChanged) {
        isOnTextChanged = false;
       //dosomething
    }

Upvotes: 10

M. Usman Khan
M. Usman Khan

Reputation: 4408

u can use a boolean check, like:

    inputBoxNumberEt.addTextChangedListener(new TextWatcher() {

        boolean ignoreChange = false;

        @Override
        public void afterTextChanged(Editable s) {
        }

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

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
            if (!ignoreChange) {
              ///Do your checks                    
                ignoreChange = true;
                inputBoxNumberEt.setText(string);
                inputBoxNumberEt.setSelection(inputBoxNumberEt.getText().length());
                ignoreChange = false;
            }
        }
    });

Upvotes: 6

Neil Townsend
Neil Townsend

Reputation: 6084

According to the developer pages for TextWatcher, if a change is made to the Editable within TextWatcher, it will trigger further calls to all the TextWatchers linked to that Editable. Now, clearly your code doesn't trigger this behaviour.

However, it is quite possible that if, for whatever reason, the system has a TextWatcher on the Editable, the situation you describe can occur. "Why", I hear you cry, "should this happen?"

First, the classic defence: there is no reason for it not to happen and, strictly, app code should be written to be resilient to it.

Second, I can't prove it, but I could well imagine that the code which handles layout of the displayed text within an EditText uses a TextWatcher to handle updating the display of the text on the screen. This code might insert control codes (which you aren't shown) into the Editable to ensure good line breaks and so on. It may even go round a loop a few times to get it right, and you might only get your first call after it has done all of its ...

EDIT

As per the comment by @Learn OpenGL ES, calling of a TextWatcher would be normal for things like autocorrect.

Upvotes: 8

Nico
Nico

Reputation: 1650

I had the same kind of problem, when I pressed backspace with cursor at the end of a continuous text, afterTextChange was called 3 times: - The first time with the correct s value - The second time with a clear value - The third time with the correct value again

After having search a lot on the web, I tried to change my EditText inputType to

android:inputType="textNoSuggestions"

Don't ask me why, but it worked, afterTextChanged is now called only once.

Upvotes: 63

Related Questions