56

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.

Community
  • 1
  • 1
AsafK
  • 2,425
  • 3
  • 32
  • 38
  • what do you want to do with that adapter? Try check `adapter.getCount() > 0` and then clear it. Because if you get adapter and clear it, adapter will be still not null. – deadfish Jul 25 '13 at 10:32
  • 1
    @deadfish The adapter is not really the question. its just a list im filling based on the text being entered in TextWatcher. The problem, as i said, is with the number of times the events on TextWatcher listener are being called. its completely random to me. – AsafK Jul 25 '13 at 19:18
  • 1
    yes, but textwatcher is called everytime you change your view which was connected with textwatcher (change text status), do you follow me? – deadfish Jul 25 '13 at 20:47
  • No, sorry. The adapter is not connected to TextWatcher. It is connected to another ListView. Changing/Clearing it should have no affect on this TextWatcher. – AsafK Jul 26 '13 at 10:03
  • @deadfish am i right about this, or am i missing something ? – AsafK Aug 07 '13 at 07:45
  • If you want you can contact with me by mail (check profile). Maybe I can help you in your project and maybe I will need check the code. – deadfish Aug 07 '13 at 07:57
  • @Akash No. It looks like a bug, cause it happens even if i remove the code inside afterTextChanged(). Let me know if you find a solution. – AsafK Aug 17 '13 at 22:06
  • @AsafK I am also facing issue of sometimes EditText gives empty text. Could not find solution. But you can try updating os if you test on device. – Geek Aug 18 '13 at 06:26
  • 2
    How many times you registered the same watcher? Because when you do addTextChangedListener() it saves each instance of watchers in list and when the text watcher(s) need to be notified it iterates through the list of watchers. – Nikola Despotoski Aug 18 '13 at 16:38
  • @NikolaDespotoski i dont think that is the problem. its a really simple activity. you can create it yourself and see. the layout contains one EditText and in onCreate the TextWatcher listener is added. thats it. also, this doesnt explain why the event handlers are triggered only once when im adding a character but with spaces they act differently. – AsafK Aug 18 '13 at 16:58
  • @AsafK I tried to reproduce the issue as simple as you said (4.0, SDK build), but my watcher is envoked as normal as it should be. See here http://pastebin.com/0UTcUnGa – Nikola Despotoski Aug 18 '13 at 19:08
  • I'm facing the exact same issue, when adding a space in the end of the text field and then deleting it the afterTextChanged is invoked 3 times - first with the right text, then with an empty string and then with the right text. As I'm trying to detect when the user deletes all of the input this offcourse makes a mess. Any solutions? – Raanan Sep 10 '13 at 15:00
  • I ran into this, and it looks related to the spell-check. Just another bug/strange behavior of Android to work around. – Learn OpenGL ES Oct 02 '13 at 16:48
  • @NikolaDespotoski I had the same issue and resolved it. in getView() of list adapter, I was adding text watcher and they were getting added multiple times as getView() gets called for all the items. So I just make sure that I add watcher only once per EditText – Bharat Dodeja Apr 21 '15 at 11:12
  • this solution works => http://stackoverflow.com/questions/31844373/saving-edittext-content-in-recyclerview – prasanthMurugan Apr 13 '16 at 05:26

10 Answers10

61

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.

Nico
  • 1,580
  • 14
  • 21
  • 1
    This happens because in cases where the user types a space, certain devices will capitalize the previous word (if it is the first word and inputType="name"). Therefore, afterTextChanged gets called twice. Once for the space, and once for the capitalization. – Tamby Kojak Apr 11 '14 at 20:34
  • 1
    This works for keyboards that respect this input type. As for foreign languages that require a suggestion bar (i.e Japanese), it will still call the method twice. – Dave Chen Sep 22 '14 at 17:15
  • got same problem, till now API 27 Asus – Budi Mulyo Nov 20 '19 at 02:53
  • Note that `android:inputType="number"` also works as in my case I needed a "money" input – Aerim Nov 24 '20 at 13:25
10
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
    }
Pang
  • 9,564
  • 146
  • 81
  • 122
velraj
  • 261
  • 4
  • 3
  • 1
    Welcome velraj - please add some text explaining the logic of your code - it will help the original poster others understand. Please read how to answer - https://stackoverflow.com/help/how-to-answer. Explain how does it differ from usman's answer for example. – micstr Apr 07 '17 at 10:10
8

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.

Neil Townsend
  • 6,024
  • 5
  • 35
  • 52
6

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;
            }
        }
    });
M. Usman Khan
  • 3,689
  • 1
  • 59
  • 69
4

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);
Ângelo Polotto
  • 8,463
  • 2
  • 36
  • 37
2

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
            }


        });
shankar_vl
  • 72
  • 6
0

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.

Guimo
  • 335
  • 2
  • 17
0

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)
    }
0

alternatively, instead of TextWatcher you can use

editTxtView.doOnTextChanged { text, start, before, count ->  }
editTxtView.doAfterTextChanged {  }
Erkan
  • 140
  • 2
  • 11
0

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