Detect if content of EditText was changed by user or programmatically?
Asked Answered
R

6

71

I would need a way to detect if the EditText has been changed by the user typing something or by the app changing the text programmatically. Any standard way of doing this? I guess I could always do something hackish like unsetting the TextWatcher before setText() and setting it back again afterwards, but there's got to be a better way of doing this... right?

I tried checking if the EditText is focused in the TextWatcher, but that was of little help since the EditTexts gets focused "semi-randomly" anyway when scrolling...

 

Background

I have a ListView with EditTexts in every listitem. I've sorted out the basic problem of storing the values for the EditTexts for reuse when the user scrolls.

I also have a TextWatcher that sums up the values in all EditTexts and displays the sum when the user edits the content of any of the EditTexts.

The problem is that when I'm scrolling the list and my custom adapter is reentering the stored values in the EditTexts on bindView(), that also triggers the TextWatchers afterTextChanged() method, causing the scrolling to lag because the summing-up-function is triggered.

Rotund answered 23/2, 2014 at 20:12 Comment(6)
you can override OnKeyDown() and inside that trigger a boolean so you know that the user is entering text and use that inside your textwatcherExteriorize
@Exteriorize Yeah I thought of ditching the TextWatcher and relying on onKeyDown() instead, but according to the documentation "Key presses in software keyboards will generally NOT trigger this listener, although some may elect to do so in some situations. Do not rely on this to catch software key presses." developer.android.com/reference/android/widget/…, android.view.KeyEvent)Rotund
@BadCash how many TextWatchers do you have?Civvies
How about setting a class variable flag when you are adjusting it in code, and in your text watcher, check that flag, and then reset it?Soapy
@Civvies One per item in the listview, they're instantiated in the newItem() method.Rotund
I had some success clearing the focus when the view is being bound. Then only react to text edits if the EditText actually has focus.Hydroscope
R
70

This sorted itself out a long time ago, but for anyone who finds their way here looking for an answer, here's what I did:

I ended up setting the Tag of the EditText to some arbitrary value right before I'm about to change it programmatically, and changing the value, and then resetting the Tag to null. Then in my TextWatcher.afterTextChanged() method I check if the Tag is null or not to determine if it was the user or the program that changed the value. Works like a charm!

Something like this:

edit.setTag( "arbitrary value" );
edit.setText( "My Text Value" );
edit.setTag(null);

and then

public void afterTextChanged(Editable s) {
    if( view.getTag() == null )             
        // Value changed by user
    else
        // Value changed by program
}
Rotund answered 9/9, 2014 at 18:13 Comment(3)
worked for me! thank you for the easy and simple solutionVibrio
This solution is amazing. This solved a struggle we've been having for over a year. The one catch is that you shouldn't check the editText's tag inside of coroutine block - be sure to set a variable outside the block and then check it inside.Chenoweth
Whats view.getTag() getting error there!?Slipperwort
L
10

The accepted answer is perfectly valid, but I have another approach;

@Override
public void onTextChanged(CharSequence charSequence, 
                         int start, int before, int count) {
    boolean userChange = Math.abs(count - before) == 1; 
    if (userChange) { 

    }
}

It works by checking if the change was a single character. This is not a fool-proof solution as copy-paste operations might be missed, and non-user changes of a single character will also be missed. Depending on your use case, this might be a viable solution.

Luff answered 30/11, 2016 at 10:59 Comment(7)
Best solution... easy to understandPeafowl
So what happens when the user pastes something in the EditText? ;)Quadruped
So what happens when the user taps on an auto suggested word?Applicable
Wrong answer. Will produce wrong results if using any cut/paste or highlighting edits. Simply wrong.Nacelle
This is wrong answer because of following reasons 1. User pastes something in as suggested by @Quadruped 2. User taps on some suggestion by Aloha Not sure why so many people has upvoted this :(Margarito
What if you programmatically change one character? Not a good solution.Shote
The user can also paste text with more than 1 char!Bucella
E
7

Depending on your use case (e.g. you are auto-populating this field when the user types into another field), you can also check if the view has focus, e.g.:

textView.doAfterTextChanged {
    val isValueChangedByUser = textView.hasFocus()
    // ...
}
Ejector answered 3/12, 2020 at 16:57 Comment(0)
G
2

One thing that helped to me is having boolean canListenInput field. Use it inside of watcher.

    email.addTextChangedListener(new TextWatcher() {
        @Override
        public void afterTextChanged(Editable s) {
            if (canListenInput) {
                emailChanged = true;
            }
        }
    });

Clear it before changing text programmatically. Set it inside of onAttachedToWindow, (after state) restoration:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    canListenInput = true;
}
Genseric answered 24/10, 2014 at 14:52 Comment(0)
M
0

I have created some extension methods to tackle this scenario

inline fun TextView.runTaggingCode(block: () -> Unit) {
    this.setTag(R.string.tag_text_id, "set_from_code")
    block()
    this.setTag(R.string.tag_text_id, null)
}

fun TextView.isTaggedForCode() = this.getTag(R.string.tag_text_id) != null

where I have defined the R.string.tag_text_id as below

<string name="tag_text_id" translatable="false">dummy</string>

Now where I to use these methods, I will simply change my code as below,

override fun beforeTextChanged(
    s: CharSequence, start: Int, count: Int,
    after: Int,
) {
    if (textView.isTaggedForCode()) {
        return
    }
    textView.runTaggingCode { 
        // your logic here
    }
}

But in case you don't want to change the same text view text, in it own TextWatcher you can also see the answer

Margarito answered 6/11, 2022 at 21:21 Comment(0)
R
-1

You can do this by adding:

private String current = "";
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(!s.toString().equals(current)){
   [your_edittext].removeTextChangedListener(this);

   //Format your string here...

   current = formatted;
   [your_edittext].setText(formatted);
   [your_edittext].setSelection(formatted.length());

   [your_edittext].addTextChangedListener(this);
}
Rammish answered 14/5, 2015 at 4:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.