9

I am using a textwatcher to check the validity of an user input. Some of my user had a crash caused by this textwacher. Here is the stacktrace given by google :

java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1

at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at android.widget.TextView.sendAfterTextChanged(TextView.java:7997)
at android.widget.TextView$ChangeWatcher.afterTextChanged(TextView.java:10043)
at android.text.SpannableStringBuilder.sendAfterTextChanged(SpannableStringBuilder.java:970)
at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:497)
at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:435)
at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:30)
at android.view.inputmethod.BaseInputConnection.replaceText(BaseInputConnection.java:679)
at android.view.inputmethod.BaseInputConnection.setComposingText(BaseInputConnection.java:437)
at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:333)
at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:77)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5584)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
at dalvik.system.NativeStart.main(Native Method)

And my code :

        Zipcode.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 (editable.toString().equals(zc)) return;
                zc = editable.toString();
                isZipcodeChecked = false;
                Log.d(TAG, "Text changed : " + editable.toString());
                if (editable.toString().length() != 5) {
                    Zipcode.setTextColor(Color.RED);
                    return;
                }
                checkZipcode(editable.toString());
            }
        });

I do not have much more info about how this happened. It works fine most of the time and I could not reproduce this bug. Any idea about what happened?

Laetan
  • 879
  • 9
  • 26

2 Answers2

6

The answer has already been given in a comment, but hard to see if you're just skimming by like me. So the real answer is:

Avoid adding the TextWatcher twice or more times, as the first one will edit the content and the subsequent ones will then cause an exception.

Since there's no way to check if an EditText already has a TextWatcher set, I've built my own solution with a boolean, that checks if the TextWatcher has already been added or not.

private EditText mEditText;
private TextWatcher mTextWatcher;
private boolean mTextWatcherIsEnabled = false;

public void setTextWatcher(TextWatcher textWatcher) {
    mTextWatcher = textWatcher;
}

public void enableTextWatcher() {
    if (!mTextWatcherIsEnabled) {
        mEditText.addTextChangedListener(mTextWatcher);
    }
    mTextWatcherIsEnabled = true;
}

public void disableTextWatcher() {
    if (mTextWatcherIsEnabled) {
        mEditText.removeTextChangedListener(mTextWatcher);
    }
    mTextWatcherIsEnabled = false;
}
Bernd Kampl
  • 4,837
  • 4
  • 21
  • 26
  • Hi! I am suffering the very same problem, but my TextWatchers do not modify the content - just reacts to its changes outside. Is it still somehow editing its content under the hood? – Michał Powłoka Apr 10 '18 at 10:30
  • @MichałPowłoka it's kind of hard to say without code. I suggest you open a new question with the error message and code and if you link to it from here I can take a look at it :) – Bernd Kampl Apr 10 '18 at 10:51
3

For me this was happening because I had two different TextWatchers added to the same view, and the one I added first was removing itself:

view.addTextChangedListener(new ListenerA());
view.addTextChangedListener(new ListenerB());
class ListenerA extends TextWatcher {

    @Override
    public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
        view.removeTextChangedListener(this);
        // ...
    }
}

Because the first one removes itself, it causes an index out of bounds exception here:

private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
    if (mListeners != null) {
        final ArrayList<TextWatcher> list = mListeners;
        final int count = list.size();
        for (int i = 0; i < count; i++) {
            list.get(i).beforeTextChanged(text, start, before, after);
        }
    }
    // ...
}

count is queried and the list has size of 2. So it calls get(0) which then removes itself. Then it calls get(1), but the list size is only 1 now, so this throws the exception.

I solved this by not actually removing my TextWatcher in the first place, and instead just disabling the behavior. You could alternatively solve this by guaranteeing that only the last watcher in the list ever removes itself.

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • I'm not removing the textwatcher and still got the exception. Also, I use `addTextChangedListener` extension, just in case. – Dr.jacky Nov 13 '20 at 13:11