Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IndexOutOfBoundsException on a textwatcher

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?

like image 722
Laetan Avatar asked Oct 08 '15 09:10

Laetan


2 Answers

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;
}
like image 68
Bernd Kampl Avatar answered Nov 08 '22 00:11

Bernd Kampl


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.

like image 44
Ben P. Avatar answered Nov 08 '22 01:11

Ben P.