1

I have a custom edit text named MyEditText, I want to add the auto closing bracket feature, so when I type "[", I close that bracket automatically and put the cursor between the brackets '[|]'. This is my code:

MyEditText:

public class MyEditText extends AppCompatEditText {

    public static final String CURSOR = "\u2622";

        private final TextWatcher bracketWatcher = new TextWatcher() {
            private int start;
            private int count;
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                this.start = start;
                this.count = count;
            }
    
            @Override
            public void afterTextChanged(Editable editable) {
                //remove cursor character and put real cursor instead: [☢] -> [|]
                if (editable.length() > start && count > 1) {
                    CharSequence newText = editable.subSequence(start, start + count);
                    int i = newText.toString().indexOf(CURSOR);
                    if (i > -1) {
                        editable.delete(start + i, start + i + 1);
                        setSelection(start);
                    }
                }
            }
        };
    
        private void init() {
            setFilters(new InputFilter[]{inputFilter});
            addTextChangedListener(bracketWatcher);
        }
    
        private CharSequence addBracket(CharSequence source, int start) {
            if (source.charAt(start) == '[') {
                return "[" + CURSOR + "]";
            }
            return source;
        }
    
        private final InputFilter inputFilter = (source, start, end, dest, dstart, dend) -> {
            try {
                if (end - start == 1 && start < source.length()) {
                    return addBracket(source, start);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return source;
        };
    
        public MyEditText(Context context) {
            super(context);
            init();
        }
    
        public MyEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    }

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyEditText editorView = findViewById(R.id.my_edit_text);

        String text = "line 1\n" +
                "line 2\n" +
                "line 3";

        editorView.setText(text);
    }
}

main_activity.xml

<androidx.constraintlayout.widget.ConstraintLayout 

    xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.abdo.stackoverflowquestionioob.MyEditText
            android:id="@+id/my_edit_text"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:gravity="start|top"
            android:hint="Write text here"
            android:layout_margin="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

In most cases, this code works! , but has a serious bug, which occurs only in a special case: When the app has just opened, I put the cursor at the beginning of the edit text (position = 0) and then click the bracket "[" (image 1).

Note that if I put the cursor in another position (position != 0) and move it to position 0 (image 2), this error will not occur!, it ONLY occurs and ONLY in the case I mentioned above.

image 1 (with error): enter image description here

image 2 (without error): enter image description here

Stack trace:

java.lang.IndexOutOfBoundsException: charAt: 22 >= length 22
        at android.text.SpannableStringBuilder.charAt(SpannableStringBuilder.java:124)
        at android.text.TextLine.handleText(TextLine.java:883)
        at android.text.TextLine.handleRun(TextLine.java:1125)
        at android.text.TextLine.drawRun(TextLine.java:491)
        at android.text.TextLine.draw(TextLine.java:286)
        at android.text.Layout.drawText(Layout.java:588)
        at android.widget.Editor.drawHardwareAcceleratedInner(Editor.java:2167)
        at android.widget.Editor.drawHardwareAccelerated(Editor.java:2086)
        at android.widget.Editor.onDraw(Editor.java:2026)
        at android.widget.TextView.onDraw(TextView.java:8754)
        at android.view.View.draw(View.java:23191)
        at android.view.View.updateDisplayListIfDirty(View.java:22066)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:5214)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:5186)
        at android.view.View.updateDisplayListIfDirty(View.java:22021)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:588)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:594)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:667)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4264)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4048)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3321)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2201)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9000)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:996)
        at android.view.Choreographer.doCallbacks(Choreographer.java:794)
        at android.view.Choreographer.doFrame(Choreographer.java:729)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:981)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:7948)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1075)

If I remove "bracketWatcher" from the code, the exception will not occur, so I think this part of the code is causing the problem:

@Override
public void afterTextChanged(Editable editable) {
    //remove cursor character and put real cursor instead: [☢] -> [|]
    if (editable.length() > start && count > 1) {
        CharSequence newText = editable.subSequence(start, start + count);
        int i = newText.toString().indexOf(CURSOR);
        if (i > -1) {
            editable.delete(start + i, start + i + 1);
            setSelection(start);
        }
    }
}

I spent a lot of time figuring out how to fix this problem. I hope that someone could help me.

Abdo21
  • 498
  • 4
  • 14
  • Maybe try `i > 0`. – Martin Zeitler Aug 29 '21 at 01:49
  • Thanks for your quick response, the exception still occurs with this change. – Abdo21 Aug 29 '21 at 02:00
  • Can you start by logging out various variables `start` , `count`, `i` etc? And then you'll most probably have to add another if. Not near my pc, will try and tell you asap – gtxtreme Aug 29 '21 at 04:07
  • Thanks for the reply, I ran the debugger on it and everything is going as expected! I have Samsung with an Android 10 device, maybe this bug only occurs on this device because I found someone reported this bug with the same stacktrace and the same device. I don't have any other device to confirm it, it will be very helpful if you can try it on your device. https://stackoverflow.com/questions/64912832/indexoutofboundsexception-editor-java-line-2182-only-on-samsung-android-10 – Abdo21 Aug 29 '21 at 04:48

1 Answers1

0

Do a try catch statement on this specific line in afterTextChanged method,
int i =newText.toString().indexOf(CURSOR);

I think that you are trying to pass and integer which exceeds the total chars in newText.toString() for example , if the length or total chars of newText.toString() is just 10 then when you try to place cursor at index 11 or anything more than 9 will fire this exception IndexOutOfBoundsException

  • Thanks for your reply, I tried as you said and the problem still persists. the line you specify cannot draw IndexOutOfBoundException. I also tried putting all the code inside try catch. – Abdo21 Aug 29 '21 at 08:32