How to remove all listeners added with addTextChangedListener
Asked Answered
B

15

82

I have a ListView where each row has an EditText control. I want to add a TextChangedListener to each row; one that contains extra data which says which row the EditText was in. The problem is that as getView gets called, multiple TextWatchers are added; because the convertView already having a TextWatcher (and one that points to a different row).

MyTextWatcher watcher = new MyTextWatcher(currentQuestion);
EditText text = (EditText)convertView.findViewById(R.id.responseText);
text.addTextChangedListener(watcher);

MyTextWatcher is my class that implements TextWatcher; and handles the text events. CurrentQuestion lets me know which row I'm acting upon. When I type in the box; multiple instances of TextWatcher are called.

Is there any way to remove the TextWatchers before adding the new one? I see the removeTextChangedListener method, but that requires a specific TextWatcher to be passed in, and I don't know how to get the pointer to the TextWatcher that is already there.

Bartholomeo answered 7/6, 2011 at 19:22 Comment(3)
Wouldn't it be simpler to have just one TextWatcher for all elements?Katy
How could I know which row was modified when the text changed method fires?Bartholomeo
@Jodes: I don't think it is better to have one TextWatcher for all elements. You need different textwatchers if you want to add/remove them.Guggenheim
D
57

There is no way to do this using current EditText interface directly. I see two possible solutions:

  1. Redesign your application so you always know what TextWatcher are added to particular EditText instance.
  2. Extend EditText and add possibility to clear all watchers.

Here is an example of second approach - ExtendedEditText:

public class ExtendedEditText extends EditText
{   
    private ArrayList<TextWatcher> mListeners = null;

    public ExtendedEditText(Context ctx)
    {
        super(ctx);
    }

    public ExtendedEditText(Context ctx, AttributeSet attrs)
    {
        super(ctx, attrs);
    }

    public ExtendedEditText(Context ctx, AttributeSet attrs, int defStyle)
    {       
        super(ctx, attrs, defStyle);
    }

    @Override
    public void addTextChangedListener(TextWatcher watcher)
    {       
        if (mListeners == null) 
        {
            mListeners = new ArrayList<TextWatcher>();
        }
        mListeners.add(watcher);

        super.addTextChangedListener(watcher);
    }

    @Override
    public void removeTextChangedListener(TextWatcher watcher)
    {       
        if (mListeners != null) 
        {
            int i = mListeners.indexOf(watcher);
            if (i >= 0) 
            {
                mListeners.remove(i);
            }
        }

        super.removeTextChangedListener(watcher);
    }

    public void clearTextChangedListeners()
    {
        if(mListeners != null)
        {
            for(TextWatcher watcher : mListeners)
            {
                super.removeTextChangedListener(watcher);
            }

            mListeners.clear();
            mListeners = null;
        }
    }
}

And here is how you can use ExtendedEditText in xml layouts:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <ua.inazaruk.HelloWorld.ExtendedEditText 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="header"
        android:gravity="center" /> 

</LinearLayout>
Discommon answered 7/6, 2011 at 19:39 Comment(1)
/face palm - wish I had thought of that. Nice!Gaddi
F
31

You can remove TextWatcher from your EditText. First of all I suggest you to move TextWatcher declaration outside the the editText.addTextChangedListener(...):

protected TextWatcher yourTextWatcher = new TextWatcher() {

    @Override
    public void afterTextChanged(Editable s) {
        // your logic here
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // your logic here
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
       // your logic here
    }
};

After that you will be able to set TextWather little bit simpler:

editText.addTextChangedListener(yourTextWatcher);

Than you can remove TextWatcher like this:

editText.removeTextChangedListener(yourTextWatcher);

and set another if you want.

Fizzy answered 12/2, 2013 at 13:46 Comment(3)
But that would mean you have to know the exact textWatcher name applied to your EditText. I believe the question was how to remove ALL textWatchers on the EditText.Guggenheim
Igor Ganapolsky You are right. But in many other cases it's a very useful stuffNorword
@Guggenheim Correct. However, I can't think of a situation where you need multiple text watchers on a single EditText..... One text watcher for multiple EditTexts is understandable, but why many for a single EditText?Mirella
I
12

I also spent a lot of time finding the solution and finally ended up solving with the help of tag like below. It would remove previous TextWatcher instances by getting references from tag of the convertView. It perfectly solves the problem. In your CustomAdapter file, set a new inner class like below:

private static class ViewHolder {

        private TextChangedListener textChangedListener;
        private EditText productQuantity;

        public EditText getProductQuantity() {
            return productQuantity;
        }    

        public TextChangedListener getTextChangedListener() {
            return textChangedListener;
        }

        public void setTextChangedListener(TextChangedListener textChangedListener) {
            this.textChangedListener = textChangedListener;
        }
    }

Then in your overrided public View getView(int position, View convertView, ViewGroup parent) method implement the logic like below:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

     EditText productQuantity;
    TextChangedListener textChangedListener;

    if(convertView==null) {
        LayoutInflater mInflater = (LayoutInflater)
                context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = mInflater.inflate(R.layout.cart_offer_item, parent, false);

        productQuantity=(EditText)convertView.findViewById(R.id.productQuantity);
        addTextChangedListener(viewHolder, position);
        convertView.setTag(viewHolder);
    }
    else
    {
        ViewHolder viewHolder=(ViewHolder)convertView.getTag();
        productQuantity=viewHolder.getProductQuantity();
        removeTextChangedListener(viewHolder);
        addTextChangedListener(viewHolder, position);
    }

    return convertView;
}



private void removeTextChangedListener(ViewHolder viewHolder)
{
    TextChangedListener textChangedListener=viewHolder.getTextChangedListener();
    EditText productQuantity=viewHolder.getProductQuantity();
    productQuantity.removeTextChangedListener(textChangedListener);
}

private void addTextChangedListener(ViewHolder viewHolder, int position)
{
    TextChangedListener textChangedListener=new TextChangedListener(position);
    EditText productQuantity=viewHolder.getProductQuantity();
    productQuantity.addTextChangedListener(textChangedListener);
    viewHolder.setTextChangedListener(textChangedListener);
}

Then implement TextWatcher class as below:

private class TextChangedListener implements TextWatcher
{
    private int position;
    TextChangedListener(int position)
    {
        this.position=position;
    }
    @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) {
    Log.d("check", "text changed in EditText");

    }
}

It would remove previous TextWatcher instances by getting references from tag of the convertView

Interception answered 20/1, 2015 at 9:18 Comment(0)
L
9

It has been long since this question was asked, but someone might find this useful. The problem with TextWatcher in Recyclerview is that we have to make sure it is removed before the view is recycled. Otherwise, we loss the instance of the TextWatcher, and calling removeTextChangedListener(textWatcher) in the OnBindViewHolder() will only remove the current instance of TextWatcher.

The way I solve this problem is to add the TextChangedListener inside a FocusChangedListener:

editText.setOnFocusChangeListener(new OnFocusChangeListener() {          
public void onFocusChange(View v, boolean hasFocus) {
    if(hasFocus) {
        editText.addTextChangedListener(textWatcher)
    }
    else{
        editText.removeTextChangedListener(textWatcher)
    }
  }
});

This way I am sure when the editText doesn't have focus then the textwatcher is removed, and added again when it has focus. So, when the recyclerview is recycled the editText will have any textChangeListener removed.

Locative answered 5/2, 2021 at 1:14 Comment(1)
It very beautiful solutionFidelity
R
6

I struggled with a similar problem with a lot of EditTexts in RecyclerView. I solved it by reflection. Call ReflectionTextWatcher.removeAll(your_edittext) before bind views. This piece of code finds all TextWatchers and removes them from the local EditText's list called "mListeners".

public class ReflectionTextWatcher {
    public static void removeAll(EditText editText) {
        try {
            Field field = findField("mListeners", editText.getClass());
            if (field != null) {
                field.setAccessible(true);
                ArrayList<TextWatcher> list = (ArrayList<TextWatcher>) field.get(editText); //IllegalAccessException
                if (list != null) {
                    list.clear();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static Field findField(String name, Class<?> type) {
        for (Field declaredField : type.getDeclaredFields()) {
            if (declaredField.getName().equals(name)) {
                return declaredField;
            }
        }
        if (type.getSuperclass() != null) {
            return findField(name, type.getSuperclass());
        }
        return null;
    }
}

I hope, this will help someone.

Retinitis answered 11/11, 2020 at 23:6 Comment(2)
Thanks you so much ! It works like a charm. It was very helpfull for me. thanks !Treacherous
It works for me!Repent
A
5

Save the current textwatcher in viewholder and you can find the one you want to remove.

Armilda answered 12/4, 2014 at 0:26 Comment(1)
If you save the current text watcher in the viewholder - next time viewholder is reused, it will have another view and an old text watcher. Thus, one will not be able to remove that old text watcher from an old view (which belongs to some other viewholder now).Tale
T
2

If one, like me, deals with ViewHolder, then simply saving a reference to a text watcher upon its creation will not help. Upon reuse the view will get to some other ViewHolder which would not have a reference to that old text watcher, thus one won't be able to delete it.

Personally i chose to solve problem like @inazaruk, though updated code to Kotlin + renamed class to better reflect it's purpose.

class EditTextWithRemovableTextWatchers(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs) {

    private val listeners by lazy { mutableListOf<TextWatcher>() }

    override fun addTextChangedListener(watcher: TextWatcher) {
        listeners.add(watcher)
        super.addTextChangedListener(watcher)
    }

    override fun removeTextChangedListener(watcher: TextWatcher) {
        listeners.remove(watcher)
        super.removeTextChangedListener(watcher)
    }

    fun clearTextChangedListeners() {
        for (watcher in listeners) super.removeTextChangedListener(watcher)
        listeners.clear()
    }
}
Tale answered 4/12, 2019 at 12:32 Comment(1)
WIll give me crashes. works with 'EditText' but not with AppCompatEditText' Attempt to invoke interface method 'java.lang.Object kotlin.Lazy.getValue()' on a null object reference E/AndroidRuntime: at androidx.emoji2.viewsintegration.EmojiEditTextHelper$HelperInternal19.<init>(EmojiEditTextHelper.java:267) at androidx.emoji2.viewsintegration.EmojiEditTextHelper.<init>(EmojiEditTextHelper.java:109)Timer
P
1

As you can see here: CodeSearch of TextView there is no way of removing all listeners. The only way is to provide the watcher you used to register it.

I do not yet fully understand why there are other listeners already registered. However you can subclass the EditText, override the addTextChangedListener(..) and in it keep a copy of all added references yourself and then delegate to the superclass implementation. You then can also provide an additional method that removes all listeners.

Get in touch if you need further explanations.

Puerperal answered 7/6, 2011 at 19:39 Comment(1)
How do you provide a registered watcher if it was in an anonymous inner class?Guggenheim
S
1

I had the same problem with xamarin/C# and I wrote for this a class to manage click events inside a ListView where the item view will be "recycled":

 public class ViewOnClickEventHandler: Java.Lang.Object
 {
    private List<EventHandler> EventList { get; set; }

    public void SetOnClickEventHandler(View view, EventHandler eventHandler)
    {
        if (view.Tag != null)
        {
            ViewOnClickEventHandler holder = ((ViewOnClickEventHandler)view.Tag);

            foreach (EventHandler evH in holder.EventList)
                view.Click -= evH;

            for (int i = 0; i < holder.EventList.Count; i++)
                holder.EventList[i] = null;

            holder.EventList.Clear();
        }

        EventList = new List<EventHandler>();
        EventList.Add(eventHandler);
        view.Click += eventHandler;
        view.Tag = this;
    }
}

You can use it in your ListView BaseAdapter GetItem method this way:

       TextView myTextView = convertView.FindViewById<TextView>(Resource.Id.myTextView);

        ViewOnClickEventHandler onClick = new ViewOnClickEventHandler();
        onClick.SetOnClickEventHandler(myTextView, new EventHandler(delegate (object sender, EventArgs e)
        {
            // Do whatever you want with the click event
        }));

The ViewOnClickEventHandler class will care about multiple events on your textview. You can also change the class for textchange events. It's the same princip. I hope this will help.

bye, nxexo007

Samarasamarang answered 3/12, 2016 at 9:35 Comment(0)
J
1

I resolved this situation without extend TextView class.

private ArrayList<TextWatcher> mEditTextWatcherList = new ArrayList<>();
private TextWatcher mTextWatcher1;
private TextWathcer mTextWatcher2;

mTextWathcer1 = 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) {}
};

mTextWathcer2 = 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) {}
};

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

    setListener(mTextWatcher1);
    setListener(mTextWatcher2);

    removeListeners();
}

private setListener(TextWatcher listener) {
    mEditText.addTextChangedListener(listener);
    mEditTextWatcherList.add(listener);
}

private removeListeners() {
    for (TextWatcher t : mEditTextWatcherList)
        mEditText.removeTextChangedListener(t);

    mEditTextWatcherList.clear();
}
Jotting answered 20/2, 2018 at 15:39 Comment(0)
C
1

I struggled with a similar problem. I solved it by saving references to my textWatchers in an ArrayList:

private final List<TextWatcher> textWatchersForProfileNameTextBox = new ArrayList<>();

public void addTextWatcherToProfileNameTextBox(TextWatcher textWatcher){
    textWatchersForProfileNameTextBox.add(textWatcher);
    getProfileNameTextView().addTextChangedListener(textWatcher);
}

public void removeAllTextWatchersFromProfileNameTextView(){
    while (!textWatchersForProfileNameTextBox.isEmpty())
        getProfileNameTextView().removeTextChangedListener(textWatchersForProfileNameTextBox.remove(0));
}
Coverlet answered 14/5, 2018 at 22:48 Comment(0)
M
1

I've run into the issue when using EditText in ViewHolder in RecyclerView item, and it was causing error of infinite loop, when ViewHolder was binding, cause the TextWatcher added in previous bind call was called, hence, never-ending loop..

The only working solution for that was to store TextWatcher's in the list, and then in onBindViewHolder, go trough that list and remove TextWatcher from the EditText.

private val textWatchers: MutableList<TextWatcher> = mutableListOf()

Add TextWatcher to list before assigning it to EditText:

textWatchers.add(textWatcher1)
vh.moneyAmount.editText?.addTextChangedListener(textWatcher1)

Remove them when binding the item, going to trough the entire textWatcherList:

private fun removeTextWatcher(vh: MoneyItemViewHolder) {
    textWatchers.forEach { vh.moneyAmount.editText?.removeTextChangedListener(it) }
}

There isn't any other way to remove the TextWatcher's from EditText, than passing the TextWatcher object, hence it needs to be stored somewhere is we plan to remove it later.

Moskow answered 23/7, 2022 at 22:45 Comment(0)
G
0

What I did to remove text watchers is very simple. I created an array to put my textwatchers:

final TextWatcher[] textWatchers = new TextWatcher[3];

I added them in:

final int CURRENT_PIN_CHECK = 0, NEW_PIN = 1, CONFIRM_PIN_CHECK = 2;

textWatchers[CURRENT_PIN_CHECK] = returnTextWatcherCheckPIN(CURRENT_PIN_CHECK);
textWatchers[NEW_PIN] = returnTextWatcherCheckPIN(NEW_PIN);
textWatchers[CONFIRM_PIN_CHECK] = returnTextWatcherCheckPIN(CONFIRM_PIN_CHECK);

My returnTextWatcherCheckPIN method instantiates a textWatcher with a different checker (switchMethod to check all four editTexts) on afterTextChanged.

Then whenever I remove a text watcher I just referenced the one from the array:

etPin4.removeTextChangedListener(textWatchers[CURRENT_PIN_CHECK]);

Check the listeners size of the editText on debug:

enter image description here

It's removed! That solved my problem!

Grain answered 2/3, 2016 at 0:50 Comment(0)
B
0

Why not attach the TextWatcher reference to the EditText itself with setTag()?

if (etTagValue.getTag(R.id.textWatcherTag) != null) {
    etTagValue.removeTextChangedListener((TextWatcher) etTagValue.getTag());
}

etTagValue.setText(myValue);
TextWatcher textWatcher = new DelayedTextWatcher(text -> meta.setDescription(text.toString()));
etTagValue.addTextChangedListener(textWatcher);
etTagValue.setTag(R.id.textWatcherTag, textWatcher);

In ids.xml under /values package:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="textWatcherTag" type="id" />
</resources>
Badajoz answered 11/4, 2021 at 14:54 Comment(0)
S
0

As I'm one of those who face EditText doOnTextChanged listener multiple listener problems in the adapter when using notifiDataSetChange. After so much research and trying lots of ways, I found a solution here in the answer section given by @Davi. And now I made an Kotlin inline function for more easy way to use, this on top of Mr @Davi s answer. Hope it will help you guys

inline fun EditText.doAfterTextChangedMultipleListenerFix(
   crossinline afterTextChanged: (text: Editable?) -> Unit
) {

   val textWatcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
        afterTextChanged.invoke(s)
    }

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

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

onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
    if (hasFocus) {
        addTextChangedListener(textWatcher)
    } else {
        removeTextChangedListener(textWatcher)
    }
 }

}

Here's how you use

youEditText.doAfterTextChangedMultipleListenerFix{text->
    //do what you want
}

And finally you must clear focus before notifi change adapter

Shelley answered 21/6, 2023 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.