helios
helios

Reputation: 13841

EditText crashing on clearing text

I want to clear the text from an EditText. I tried:

and it works.But sometimes there’s an exception.

2018-12-11 16:23:44.367 13388-13388/com.example.helios.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.helios.myapplication, PID: 13388
    java.lang.IndexOutOfBoundsException: setSpan (0 ... 6) ends beyond length 0
        at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1265)
        at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
        at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:677)
        at android.widget.Editor$SuggestionsPopupWindow.updateSuggestions(Editor.java:3608)
        at android.widget.Editor$SuggestionsPopupWindow.show(Editor.java:3480)
        at android.widget.Editor.replace(Editor.java:359)
        at android.widget.Editor$3.run(Editor.java:2129)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6119)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776

I execute that code from an overridden performClick() (I'm creating a subclass of EditText).

It seems that EditText doesn’t realize it should cancel/forget the suggestion mechanism and it fails.

Do you know if there's anything special I should do to cancel/prevent suggestions to fail?

I noticed that the exception happens if I make the popup window to appear, then click twice at X (first one dismiss the popup, second one executes the clearing code).

Update:

Relevant parts of the code. The actual code has a plugin system to add different drawings and actions, but the relevant code is this one:

    class MyEditText(..): EditText(context) {
    ... 
        override fun onTouchEvent(event: MotionEvent?): Boolean {
            event!!
            ensureDrawingInfo()
            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                    lastTouchArea = pluginAreaFromMotionEvent(event)
                }
            }
            return super.onTouchEvent(event)
        }

        override fun performClick(): Boolean {
            lastTouchArea?.let {
                plugins[it.pluginIndex].onClick(this, it.rightSide, ::invalidateDrawingInfo)
                return true
            }
            return super.performClick()
        }


    class MyPlugin(val drawableLeft: Drawable, val drawableRight: Drawable) : Plugin {
        ....

        override fun onClick(editText: MyEditText, rightSide: Boolean, redraw: () -> Unit) {
            if (rightSide) {
                // X
                editText.text.clearSpans()
                editText.text.clear()
            }
        }
    }

Upvotes: 3

Views: 867

Answers (2)

Basilliy Chirko
Basilliy Chirko

Reputation: 56

Problem is "updateSuggestions" try to setSpan after you change text in EditText. So you need make text change after editor modify EditText. Just put your code in view.post

Kotlin

mEditText.post {
   mEditText.setText("your text")
}

Java

mEditText.post(new Runnable() {
        @Override
        void run() {
            mEditText.setText("your text");
        }
    });

Upvotes: 4

helios
helios

Reputation: 13841

I found a way to avoid the problem: consuming the touch events I'm interested in.

I want to react to clicks on certain corner of the edit box (think of a clear text button, or a view password one).

I first tried to use only onTouchEvent because I needed the coordinates. But the IDE warned me about not overriding performClick too for accessibility reasons. The idea is if you add extra behavior on touch, it should be available also to accessibility tools that call performClick.

But it's not the case. The extra buttons are extra functionality that doesn't need to be available. Or, if it is available, it won't be available as a normal/general click on the edit box. Maybe as an accessibility action implemented somewhere else.

So, conclusion: I can use only onTouchEvent and consume the events.

Solution

override fun onTouchEvent(event: MotionEvent?): Boolean {
        event!!
        ensureDrawingInfo()
        val touchArea = pluginAreaFromMotionEvent(event)
        if (touchArea != null) {
          // it's the special place, I will handle this
          when (event.actionMasked) {
              MotionEvent.ACTION_DOWN -> lastTouchArea = touchArea
              MotionEvent.ACTION_CANCEL -> lastTouchArea = null
              MotionEvent.ACTION_UP -> {
                if (lastTouchArea == touchArea) { // proper click on place
                  // call the plugin
                  plugins[touchArea.pluginIndex].onClick(this, it.rightSide, ::invalidateDrawingInfo)
              }
          }
          // consume touch events that happen on MY area
          return true
        }
        return super.onTouchEvent(event)
    }

Why

The EditText editor opens sometimes a suggestion window in response to touch events. And it wants to replace text in certain position. But it doesn't replace it right away. It enqueues a message to be processed later, using a looper) so when the replace takes place the text might have been changed by me. I even tried to enqueue my clearing code in the hope that it happens later, but I was unlucky.

The cleanest solution is to consume the events relative to the areas I'm handling so the editor doesn't provoke any problems.

In fact, running this on an API 27 emulator I couldn't reproduce the error. I guess the editor code changed to be aware that the text could have been changed in between.

Upvotes: 0

Related Questions