How can I change the EditText text without triggering the Text Watcher?

JavaAndroidEvent HandlingInfinite Loop

Java Problem Overview


I have an EditText field with a Customer Text Watcher on it. In a piece of code I need to change the value in the EditText which I do using .setText("whatever").

The problem is as soon as I make that change the afterTextChanged method gets called which created an infinite loop. How can I change the text without it triggering afterTextChanged?

I need the text in the afterTextChanged method so don't suggest removing the TextWatcher.

Java Solutions


Solution 1 - Java

Short answer

You can check which View currently has the focus to distinguish between user and program triggered events.

EditText myEditText = (EditText) findViewById(R.id.myEditText);

myEditText.addTextChangedListener(new TextWatcher() {
    
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (myEditText.hasFocus()) {
            // is only executed if the EditText was directly changed by the user
        }
    }

    //...
});

Long answer

As an addition to the short answer: In case myEditText already has the focus when you programmatically change the text you should call clearFocus(), then you call setText(...) and after you you re-request the focus. It would be a good idea to put that in a utility function:

void updateText(EditText editText, String text) {
    boolean focussed = editText.hasFocus();
    if (focussed) {
        editText.clearFocus();
    }
    editText.setText(text);
    if (focussed) {
        editText.requestFocus();
    }
}

For Kotlin:

Since Kotlin supports extension functions your utility function could look like this:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}

Solution 2 - Java

You could unregister the watcher, and then re-register it.

Alternatively, you could set a flag so that your watcher knows when you have just changed the text yourself (and therefore should ignore it).

Solution 3 - Java

Java:

public class MyTextWatcher implements TextWatcher {
    private EditText editText;

    // Pass the EditText instance to TextWatcher by constructor
    public MyTextWatcher(EditText editText) {
        this.editText = editText;
    }

    @Override
    public void afterTextChanged(Editable e) {
        // Unregister self before update
        editText.removeTextChangedListener(this);

        // The trick to update text smoothly.
        e.replace(0, e.length(), e.toString());

        // Re-register self after update
        editText.addTextChangedListener(this);
    }

    ...
}

Kotlin:

class MyTextWatcher(private val editText: EditText) : TextWatcher {

  override fun afterTextChanged(e: Editable) {
    editText.removeTextChangedListener(this)
    e.replace(0, e.length, e.toString())
    editText.addTextChangedListener(this)
  }

  ...
}

Usage:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

You may feel a little bit lag when entering text rapidly if you are using editText.setText() instead of editable.replace().

Solution 4 - Java

Easy trick to fix ... as long a your logic to derive the new edit text value is idempotent (which it probably would be, but just saying). In your listener method, only modify the edit text if the current value is different than the last time you modified the value.

e.g.,

TextWatcher tw = new TextWatcher() {
  private String lastValue = "";

  @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) {

    // Return value of getNewValue() must only depend
    // on the input and not previous state
    String newValue = getNewValue(editText.getText().toString());
    if (!newValue.equals(lastValue)) {
      lastValue = newValue;
      
      editText.setText(newValue);
    }
  }
};

Solution 5 - Java

You can use Kotlin DSL syntax to have the generic solution for this:

fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
    this.removeTextChangedListener(textWatcher)
    codeBlock()
    this.addTextChangedListener(textWatcher)
}

And inside your TextWatcher, you can use it as:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}

Solution 6 - Java

I use that way:

mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {}

            @Override
            public void afterTextChanged(Editable s) {
                if (mEditText.isFocused()) { //<-- check if is focused 
                    mEditText.setTag(true);
                }
            }
        });

And every time you need to change text programatically, first clear the focus

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);

Solution 7 - Java

This works good for me

EditText inputFileName; // = (EditText)findViewbyId(R.id...)
inputFileName.addTextChangedListener(new TextWatcher() {
        public void afterTextChanged(Editable s) {
            
            //unregistering for event in order to prevent infinity loop
            inputFileName.removeTextChangedListener(this);
            
            //changing input's text
            String regex = "[^a-z0-9A-Z\\s_\\-]";
            String fileName = s.toString();
            fileName = fileName.replaceAll(regex, "");
            s.replace(0, s.length(), fileName); //here is setting new text
            
            Log.d("tag", "----> FINAL FILE NAME: " + fileName);
            
            //registering back for text changes
            inputFileName.addTextChangedListener(this);
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

        public void onTextChanged(CharSequence s, int start, int before, int count) { }
    });

Solution 8 - Java

The problem can be easily solved using tag filed and you don't even have to deal with editText's focus.

Setting the text and the tag programmatically

editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null

Checking for the tag in onTextChanged

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    if (editText.tag == null) {
       // your code
    }
}

Solution 9 - Java

If you need to stay focused on EditText change text you could request focus:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}

Solution 10 - Java

It's easy just do it like this

editText.addTextChangedListener(object : TextWatcher {

        private var isEditing = false

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {


        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

        }

        override fun afterTextChanged(s: Editable?) {
            if(!isEditing){
                isEditing = true
                editText.setText("Hello World!")
                isEditing = false
            }

        }
    })

in this way it don't stock in infinite loop

Solution 11 - Java

try this logic: I wanted to setText("") without going to infinite loop and this code works for me. I hope you can modify this to fit your requirement

        final EditText text= (EditText)findViewById(R.id.text);
        text.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }
        @Override
        public void afterTextChanged(Editable s) {
            if(s.toString().isEmpty())return;
            text.setText("");
            //your code
        }
    });

Solution 12 - Java

Here's a handy class that provides a simpler interface than TextWatcher for the normal case of wanting to see changes as they occur. It also allows for ignoring the next change as the OP requested.

public class EditTexts {
    public final static class EditTextChangeListener implements TextWatcher {
        private final Consumer<String> onEditTextChanged;
        private boolean ignoreNextChange = false;
        public EditTextChangeListener(Consumer<String> onEditTextChanged){
            this.onEditTextChanged = onEditTextChanged;
        }
        public void ignoreNextChange(){
            ignoreNextChange = true;
        }
        @Override public void beforeTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void onTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void afterTextChanged(Editable s) {
            if (ignoreNextChange){
                ignoreNextChange = false;
            } else {
                onEditTextChanged.accept(s.toString());
            }
        }
    }
}

Use it like this:

EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);

Whenever you want to modify the contents of editText without causing a cascade of recursive edits, do this:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener

Solution 13 - Java

My variant:

public class CustomEditText extends AppCompatEditText{
    TextWatcher l;

    public CustomEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setOnTextChangeListener(TextWatcher l) {
        try {
            removeTextChangedListener(this.l);
        } catch (Throwable e) {}
        addTextChangedListener(l);
        this.l = l;
    }

    public void setNewText(CharSequence s) {
        final TextWatcher l = this.l;
        setOnTextChangeListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        setText(s);
        post(new Runnable() {
            @Override
            public void run() {
                setOnTextChangeListener(l);
            }
        });
    }


}

Set listeners only using setOnTextChangeListener() and set text only using setNewText (I wanted to override setText(), but it is final)

Solution 14 - Java

I've created an abstract class which mitigates the cyclic issue of when a modification to the EditText is made via a TextWatcher.

/**
 * An extension of TextWatcher which stops further callbacks being called as a result of a change
 * happening within the callbacks themselves.
 */
public abstract class EditableTextWatcher implements TextWatcher {

    private boolean editing;

    @Override
    public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (editing)
            return;

        editing = true;
        try {
            beforeTextChange(s, start, count, after);
        } finally {
            editing = false;
        }
    }

    abstract void beforeTextChange(CharSequence s, int start, int count, int after);

    @Override
    public final void onTextChanged(CharSequence s, int start, int before, int count) {
    if (editing)
        return;

        editing = true;
        try {
            onTextChange(s, start, before, count);
        } finally {
            editing = false;
        }
    }

    abstract void onTextChange(CharSequence s, int start, int before, int count);

    @Override
    public final void afterTextChanged(Editable s) {
        if (editing)
            return;

        editing = true;
        try {
            afterTextChange(s);
        } finally {
            editing = false;
        }
    }    

    public boolean isEditing() {
        return editing;
    }

    abstract void afterTextChange(Editable s);
}

Solution 15 - Java

Very simple, set text with this method

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}

Solution 16 - Java

My solution for this is a lot like the others only it's my custom spin on it using viewbindings

I created the following TextWatcher

class ControlledTextWatcher(
    private val parent: TextView,
    private val onChange: ((text: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?,
    private val beforeChange: ((text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null,
    private val afterChange: ((editable: Editable?) -> Unit)? = null
) : TextWatcher {
    init {
        parent.addTextChangedListener(this)
    }

    private var enabled = true

    var text: String?
        get() = parent.value
        set(value) {
            this.enabled = false
            parent.text = value
            this.enabled = true
        }

    var res: Int
        get() = throw RuntimeException("String resource cannot be retrieved after being set")
        set(value) {
            parent.text = parent.context.getString(value)
        }


    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        if (enabled)
            beforeChange?.invoke(s, start, count, after)
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        if (enabled)
            onChange?.invoke(s, start, before, count)
    }

    override fun afterTextChanged(s: Editable?) {
        if (enabled)
            afterChange?.invoke(s)
    }

    fun detach() {
        parent.removeTextChangedListener(this)
    }
}

and I use it mainly with view bindings like so

class TestActivity : AppCompatActivity() {

class TestActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTestBinding
    private val edit by lazy { ControlledTextWatcher(binding.text, this::textChanged }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

so when I wish to make changes to the actual EditText I use the text or res attribute of the ControlledTextWatcher like so:

edit.text = "hello world" //this does not trigger the text watcher

but when the user alters the EditText it will trigger

unfortunatelly with this solution if you want to alter other parameters of the EditText, you either have to get the original EditText through bindings or copy those functions to the ControlledTextWatcher

also you have to be careful when making changes in afterChange because the change is posted to the TextView so you may end up with an endless loop

Solution 17 - Java

You should ensure your implementation of text changes is stable and does not change the text if no change is needed. Normally that would be any content that's already been through the watcher once.

The most common mistake is to set a new text in the associated EditText or the Editable even though the text was not actually changes.

On top of that, if you make your changes to the Editable instead of some specific View, you can easily resuse your watcher, and also you can test it in isolation with some unit tests to ensure it has the outcome you want.

Since Editable is an interface you could even use a dummy implementation of it that throws a RuntimeException if any of its methods are called that try to change its contents, when testing content that should be stable.

Solution 18 - Java

My way to do the thing:

In the write segment

        EditText e_q;

        e_q = (EditText) parentView.findViewWithTag("Bla" + i);

        int id=e_q.getId();
        e_q.setId(-1);
        e_q.setText("abcd...");
        e_q.setId(id);

The listener

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

        int id = view.getId();
        if(id==-1)return;

        ....

Works anyway.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1143767View Question on Stackoverflow
Solution 1 - JavaWilli MentzelView Answer on Stackoverflow
Solution 2 - JavaCasualTView Answer on Stackoverflow
Solution 3 - JavaChan Chun HimView Answer on Stackoverflow
Solution 4 - JavaJeffrey BlattmanView Answer on Stackoverflow
Solution 5 - JavaAlex MisiuliaView Answer on Stackoverflow
Solution 6 - JavaArthur MeloView Answer on Stackoverflow
Solution 7 - Javauser3361395View Answer on Stackoverflow
Solution 8 - JavaLevon PetrosyanView Answer on Stackoverflow
Solution 9 - JavaMilos Simic SimoView Answer on Stackoverflow
Solution 10 - JavaArian ShahpasandView Answer on Stackoverflow
Solution 11 - JavaShAkKiRView Answer on Stackoverflow
Solution 12 - JavaJohnnyLambadaView Answer on Stackoverflow
Solution 13 - JavakandiView Answer on Stackoverflow
Solution 14 - JavaEurig JonesView Answer on Stackoverflow
Solution 15 - JavautruccehView Answer on Stackoverflow
Solution 16 - JavaCrucesView Answer on Stackoverflow
Solution 17 - JavathoutbeckersView Answer on Stackoverflow
Solution 18 - JavaPaoloView Answer on Stackoverflow