rbing
rbing

Reputation: 133

IndexOutOfBoundsException in TextWatcher

In my app I want to track the input in an EditText. Each time the user modifies a character in the EditText I want to track:

  1. If a character was added/deleted
  2. The index at/from which the character was added/deleted and
  3. what character was added/deleted

I use TextWatcher in order to achieve this. My approach has been working fine till now. But recently I have had 3 crashes with IndexOutOfBoundsException in my production app. One thing that I have noted is that all the crashes occurred on a Galaxy S7 with OS version 6.0.1.

I am trying to figure out a good way to understand and fix this crash. Please refer the code below

public class EditTextMonitor extends Activity implements TextWatcher {
    private EditText etInputText;

    private int deleteCharIndex = -1, addCharIndex = -1;
    private char deleteChar;
    private boolean wasCharDeleted;
    private String prevCharSeq = "";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        etInputText = (EditText) findViewById(R.id.etInputText);
    }

    // Add TextChangedListener here to prevent call to text watcher methods upon switching
    // orientation
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        etInputText.addTextChangedListener(this);
    }

    /*
      This method is called to notify you that, within s, the count characters beginning at
      start have just replaced old text that had length before. It is an error to attempt to
      make changes to s from this callback.
    */
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    /*
      This method is called to notify you that, within s, the count characters beginning at start
      are about to be replaced by new text with length after.
      It is an error to attempt to make changes to s from this callback.
    */
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // if count > after then its a char delete cause the no. of chars in 's'
        // will decrease when the text change is complete.
        // If count < after
        // then its a char addition cause the no. of chars in 's'
        // will increase when the text change is complete.
        if (count > after) {
            // CHARACTER DELETION
            // As 'count' is no. of chars being replaced from 'start' by
            // 'after' no. of chars, (start+count)-1 will be the index of the
            // char that that will be deleted.
            deleteCharIndex = (start + count) - 1;
            deleteChar = s.charAt(deleteCharIndex);
            wasCharDeleted = true;
        } else if (count < after) {
            // CHARACTER ADDITION
            // As 'count' is no. of chars being replaced from 'start' by
            // 'after' no. of chars, (start+after)-1 will will be the index of the
            // char that will be added.
            wasCharDeleted = false;
            addCharIndex = (start + after) - 1;
        } else {
            // Extra call to the text changed method with no change in 's'.
            // Android framework bug??
            wasCharDeleted = false;
            Log.d(TAG, "------EXTRA CALL TO BEFORETEXTCHANGED------");
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
        // Don't process if redundant call to afterTextChanged method.
        // A framework bug??
        if (!prevCharSeq.equals(s.toString())) {
            if (wasCharDeleted) {
                // handle char delete

                if (deleteChar == 'x'
                        && (s.length() == 0 || s.charAt(deleteCharIndex - 1) == ' ')) {
                    // Business logic to deal with the case where the deleted char was an independent 'x'
                } else {
                    // Business logic to deal with the case where deleted char was anything other than an independent 'x'.
                }
            } else {
                // handle char addition

                ***if (s.charAt(addCharIndex) == 'x'// CRASH OCCURS ON THIS LINE <-----------------***
                        && (s.length() == 1 || s.charAt(addCharIndex - 1) == ' ')) {
                    // Business logic to deal with the case where added char is an independent 'x'
                } else {
                    // Business logic to deal with the case where added char is anything other than an independent 'x'
                }
            }
        }
        prevCharSeq = s.toString();
    }
}

The 3 crashes so far have been:

java.lang.IndexOutOfBoundsException:  charAt: 24 >= length 23

java.lang.IndexOutOfBoundsException:  charAt: 202 >= length 198

java.lang.IndexOutOfBoundsException:  charAt: 1 >= length 1

Looking at the indices in the exception, I assume that the addCharIndex is not getting calculated correctly. This means that either my understanding of the parameters of beforeTextChanged is incorrect or the TextWatcher methods are not being called in the expected order or they are called multiple times with updated arguments which is messing up my logic to calculate the addCharIndex in beforeTextChanged.

Any help and insight on how to address this issue will be appreciated.

Thanks.

Upvotes: 0

Views: 538

Answers (1)

Pztar
Pztar

Reputation: 4759

It's not a device issue. It's a logic issue.

Your problem actually occurs on the line before that. Because when you delete characters you don't update your addCharIndex. If the previous index was 10 and you delete 8 characters, your index is still 10 but you only have a length of 2.

If you type in xd into your input then delete the x you'll get a crash as a result of indexOutOfBoundsException that's because your if statement does the following:

s.charAt(deleteCharIndex - 1) == ' '

but since you've deleted the x character your length is now 1 but you're checking for 0 OR the above statement which is how you get your error. If you didn't know if you have an OR (||) in an if statement, it will check every condition until it hits a true result.

There are many ways to fix it, one way is to check that s.length() > 1 this way when you do deleteCharIndex - 1 the lowest point you can hit is 0.

Upvotes: 3

Related Questions