EditText in ListView is updated by onTextChanged when scrolling
Asked Answered
O

3

7

I have been searching for an answer to this, but the solutions don't seem to work for me. I have a TextView and an EditText in a list item. I am trying to update the stored values for the EditTexts when the user edits them.

@Override
public View getView(int index, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    final int pos = index;
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.user_details_list_row, parent,false);
        holder = new ViewHolder();
        holder.mCaptionTextView  = (TextView)convertView.findViewById(id.user_detail_row_caption);
        holder.mDetailEditText = (EditText)convertView.findViewById(id.user_detail_row_value);
        convertView.setTag(holder);
    }else{
        holder = (ViewHolder) convertView.getTag();
    }

    holder.mDetailEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
            // TODO Auto-generated method stub

        }

        @Override
        public void afterTextChanged(Editable s) {              
            mUserDetails.set(pos, s.toString());
        }
    });     

    holder.mCaptionTextView.setText(mUserCaptions.get(index));
    holder.mDetailEditText.setText(mUserDetails.get(index),BufferType.EDITABLE);

    return convertView;
}

public static class ViewHolder{
    public TextView mCaptionTextView;
    public EditText mDetailEditText;
}

When I do this, scrolling triggers the TextWatcher and updates the values, overwriting correct text with duplicate text from one of the other EditTexts.

Instead of a TextWatcher, I've also tried this code:

holder.mDetailEditText.setOnFocusChangeListener(new OnFocusChangeListener() {

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (!hasFocus){
        EditText et =   (EditText)v.findViewById(id.user_detail_row_value);
        mUserDetails.set(index, et.getText().toString().trim());
    }
}
});

And it also updates the wrong EditTexts. What am I missing here?

Edit: Also tried this:

        final ViewHolder testHolder = holder; 
    holder.mDetailEditText.setOnFocusChangeListener(new OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus){
                EditText et =   (EditText)v.findViewById(id.user_detail_row_value);
                mUserDetails.set(testHolder.ref, et.getText().toString().trim());
            }
        }
    });

It corrects the scrolling change issue I was seeing, but now after editing one of the EditTexts, it changes a bunch of the others as well.

Ornithischian answered 6/1, 2014 at 20:0 Comment(0)
R
3

Check your index value if it matches with the row position you are working on. If you use the holder pattern maybe that value wont change from 0 to 7-8 depending in your screen size. Something like that happened to me once, I think I solved it by implementing the getCount method. This way the row 50 gave me the position 50 and not 7 (for example).

Remount answered 6/1, 2014 at 20:7 Comment(3)
I do have the getCount implemented. It is returning the size of the data, so that should be fine.Ornithischian
Oh I see, I think that the problem could be the index value you are setting in your onFocusChange function. Anytime you set the value of the EditText try to set its index aswell using the setTag method. Then inside your onFocusChange method recover it using the getTag method.Remount
Ah, OK, adding the index to the EditText itself addressed the whole thing. Thanks.Ornithischian
B
36

The problem is related to the fact that you are not removing the previously added text watcher from the EditText widget. Instead, you keep appending new watchers to the list. Once you attempt to edit widget content all text watchers get notified sequentially, resulting in wrong user details being updated.

EditText/TextView does not provide a way to remove previously added text watchers without having an explicit reference to them. This means you will have to rework your code to keep references to text watchers and to either create/add/remove watchers for every getView method execution or to extend TextWatcher allowing altering user details to update once it gets fired. The latter is implemented below.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.user_details_list_row, parent,false);
        holder = new ViewHolder();
        holder.mCaptionTextView  = (TextView)convertView.findViewById(id.user_detail_row_caption);
        holder.mWatcher = new MutableWatcher();
        holder.mDetailEditText = (EditText)convertView.findViewById(id.user_detail_row_value);
        holder.mDetailEditText.addTextChangedListener(holder.mWatcher);
        convertView.setTag(holder);
    } else{
        holder = (ViewHolder) convertView.getTag();
    }

    holder.mCaptionTextView.setText(mUserCaptions.get(position));

    holder.mWatcher.setActive(false);
    holder.mDetailEditText.setText(mUserDetails.get(position),BufferType.EDITABLE);
    holder.mWatcher.setPosition(position);
    holder.mWatcher.setActive(true);

    return convertView;
}

static class ViewHolder{
    public TextView mCaptionTextView;
    public EditText mDetailEditText;
    public MutableWatcher mWatcher;
}

class MutableWatcher implements TextWatcher {

    private int mPosition;
    private boolean mActive;

    void setPosition(int position) {
        mPosition = position;
    }

    void setActive(boolean active) {
        mActive = active;
    }

    @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 (mActive) {
            mUserDetails.set(mPosition, s.toString());
        }
    }
}

Hope this helps.

Ballou answered 7/1, 2014 at 7:37 Comment(0)
R
3

Check your index value if it matches with the row position you are working on. If you use the holder pattern maybe that value wont change from 0 to 7-8 depending in your screen size. Something like that happened to me once, I think I solved it by implementing the getCount method. This way the row 50 gave me the position 50 and not 7 (for example).

Remount answered 6/1, 2014 at 20:7 Comment(3)
I do have the getCount implemented. It is returning the size of the data, so that should be fine.Ornithischian
Oh I see, I think that the problem could be the index value you are setting in your onFocusChange function. Anytime you set the value of the EditText try to set its index aswell using the setTag method. Then inside your onFocusChange method recover it using the getTag method.Remount
Ah, OK, adding the index to the EditText itself addressed the whole thing. Thanks.Ornithischian
T
0

I've had that same problem, when I move the scrolling to my list up / down the text value of my editText changed,then my solution was that in my EditText add a setOnFocusChangeListener and within it add a addTextChangedListener but relationed whit other instance of the same editText.

EditText etSumScans;

@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
         view = convertView;

        if (view == null)
            view = LayoutInflater.from(myContextItem).inflate(resourceLayoutItem, null);

        shipmentLinesDTO = myListItem.get(position);

        etSumScans = view.findViewById(R.id.etSumScans);
        etSumScans.setText(shipmentLinesDTO.getAttribute1() );

        etSumScans.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus){
                    EditText secondInstanceEditText= v.findViewById(R.id.etSumScans);
                    secondInstanceEditText.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) {
                            getItem(position).setAttribute1(s.toString());
                        }
                    });
                }
            }
        });
    }

With this I solved my problem. I hope helps.

Tamekia answered 30/10, 2020 at 18:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.