106

I have list item with EditText in it, I don't know how many items there will be. I have a problem when I enter some text in EditText, and then scroll down a RecyclerView, after I've scroll up again there is no text in my first EditText.

I am wondering what, and where should I write code so that while the user is typing or finished typing (I was thinking to do that with a TextWatcher) in the EditText the text gets saved into into a file (I'll save it in a .txt file in the external storage)

Am I supposed to do so in the onCreate method of the activity or in the adapter class or elsewhere?

Here is some code

Main Activity code

 public class MainActivity extends Activity {

    RecyclerView mRecyclerView;
    MyAdapter mAdapter;
    String[] mDataSet= new String[20];
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // generating text for editText Views
        for (int i = 0; i<=19; i++){
        mDataSet[i]= "EditText n: "+i;

    }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mAdapter = new MyAdapter(mDataSet);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setHasFixedSize(true);
    }

My adapter code

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

private String[] mDataset;


public static class ViewHolder extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public EditText mEditText;

    public ViewHolder(View v) {
        super(v);

        mEditText = (EditText) v.findViewById(R.id.list_item_edittext);
    }
}

public MyAdapter(String[] myDataset) {
    mDataset = myDataset;
}

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                 int viewType) {

    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.list_item, parent, false);

    ViewHolder vh = new ViewHolder(v);
    return vh;
}

@Override
public void onBindViewHolder(ViewHolder holder,  final int position) {
    holder.mEditText.setText(mDataset[position]);

    //without this addtextChangedListener my code works fine ovbiusly
    // not saving the content of the edit Text when scrolled
    // If i add this code then when i scroll all textView that go of screen
    // and than come back in have messed up content
    holder.mEditText.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
           //setting data to array, when changed
           // this is a semplified example in the actual app i save the text
           // in  a .txt in the external storage
           mDataset[position] = s.toString();
        }

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

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });

}

@Override
public int getItemCount() {
    return mDataset.length;
}

without this "addtextChangedListener" my code works fine obviusly not saving the content of the edit Text when scrolled. If i add this code, when i scroll all editText views that go off screen and than come back in have messed up content.

gatteo
  • 1,382
  • 2
  • 10
  • 17

17 Answers17

196

The major problem with your solution is allocating and assigning TextWatcher in onBindViewHolder which is an expensive operation that will introduce lags during fast scrolls and it also seems to interfere with determining what position to update in mAdapter.

Making all operations in onCreateViewHolder is a more preferable option. Here is the complete tested working solution:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        // pass MyCustomEditTextListener to viewholder in onCreateViewHolder
        // so that we don't have to do this expensive allocation in onBindViewHolder
        ViewHolder vh = new ViewHolder(v, new MyCustomEditTextListener());

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        // update MyCustomEditTextListener every time we bind a new item
        // so that it knows what item in mDataset to update
        holder.myCustomEditTextListener.updatePosition(holder.getAdapterPosition());
        holder.mEditText.setText(mDataset[holder.getAdapterPosition()]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }


    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public EditText mEditText;
        public MyCustomEditTextListener myCustomEditTextListener;

        public ViewHolder(View v, MyCustomEditTextListener myCustomEditTextListener) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);
            this.myCustomEditTextListener = myCustomEditTextListener;
            this.mEditText.addTextChangedListener(myCustomEditTextListener);
        }
    }

    // we make TextWatcher to be aware of the position it currently works with
    // this way, once a new item is attached in onBindViewHolder, it will
    // update current position MyCustomEditTextListener, reference to which is kept by ViewHolder
    private class MyCustomEditTextListener implements TextWatcher {
        private int position;

        public void updatePosition(int position) {
            this.position = position;
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            // no op
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            mDataset[position] = charSequence.toString();
        }

        @Override
        public void afterTextChanged(Editable editable) {
            // no op
        }
    }
}
dkarmazi
  • 3,199
  • 1
  • 13
  • 25
  • Thank you! it works perfectly now! One more thing...do i have to make significant changes if i what to save it into external storage and not a String array ? Because i've tried to do it in the onTextChanged but i'm sure it's not the way it shoud be done – gatteo Aug 07 '15 at 13:12
  • 4
    You are the most welcome! Regarding your question, saving data to external storage (whether writing to disk or through network) is a very expensive operation and must be performed off the UI thread. Thus, you have to do that asynchronously and reduce the number of these calls to a minimum. Since you have 20 EditTextViews, making a separate assync call for each is even more expensive. What you can do is save locally to String[] just like you do it now. When you leave this screen, sync local String[] to your external storage. When you come back to this screen, pull String[] from external storage – dkarmazi Aug 07 '15 at 13:24
  • Hi, this solution works well, but what if I have more than one edit text in each row of the recycler view? – Varun Agarwal Oct 09 '15 at 17:23
  • 4
    Varun, in that case, you'd have to add a second editText to the layout, add it to the viewHolder, create a second MyCustomEditTextListener to listen to changes in the second editText object. Most important, you will need to create a new data structure identical to mDataset to remember values for the second editTexts. – dkarmazi Oct 11 '15 at 11:33
  • hmmm solution didn't work for me :( still erases edit text when I scroll down. – Doug Ray Feb 25 '16 at 01:41
  • @DougRay, post your code or link to it, it's hard to tell what goes wrong without seeing the code. – dkarmazi Feb 27 '16 at 17:29
  • @dkarmazi Why no need call `notifyItemChanged(index);` when we update content of adapter ? – quangson91 Mar 09 '16 at 02:47
  • @quangson91, I'm not sure if I completely understand the question. Could you please point to where specifically we update the content of adapter? – dkarmazi Mar 09 '16 at 05:08
  • @dkarmazi I mean: ` @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { mDataset[position] = charSequence.toString(); }` you can see mDateset is changed value at `position`. So why we no need call `notifyItemChanged` – quangson91 Mar 09 '16 at 05:45
  • 1
    @quangson91 we don't have to call notifyItemChanged because it's an EditText view that we currently see on the screen. This view will display whatever was the last user's input while the view is visible. Now, mDataset becomes handy when the view goes out of the screen and then comes back. In case we had something like a regular TextView, then notifyItemChanged would be necessary to call to update the current displayed value of the view. – dkarmazi Mar 09 '16 at 16:15
  • Ok, So if content visible is same mDateset then we no need call notify to adapter – quangson91 Mar 09 '16 at 16:22
  • @quangson91 only in case you're dealing with EditText type of view, because you had literally typed the new content into this text box and then saved it to mDateset – dkarmazi Mar 10 '16 at 04:50
  • @dkarmazi Ok, So for another case we must call `notifyItemChange`. – quangson91 Mar 10 '16 at 06:10
  • It's work. but when i add texts in one of the edittext, the some other rows duplicates the same value. Is there any solution for this? – ARUNBALAN NV May 31 '16 at 05:48
  • @ARUNBALANNV, this behavior should not happen and solution has been designed to address that very issue. Can you post your somewhere so that I can see what's going on? – dkarmazi May 31 '16 at 15:41
  • +1 Great answer, solved my issue. But I am facing an issue in this, when I scroll the RecyclerView the soft keyboard changed from numeric to alphanumeric and also the EditText looses focus. – Yogesh Suthar Jun 11 '16 at 12:43
  • 3
    UPDATE: should use holder.getAdapterPosition() instead of position in: holder.myCustomEditTextListener.updatePosition(**position**); – Ninja Coding Jun 17 '16 at 14:04
  • As far as I can tell, this doesn't work at all. Given that onTextChanged is called when the EditText's holder gets recycled, it will just update the saved values to empty values, thus losing the data – JamEngulfer Jul 25 '16 at 01:51
  • 1
    Would it be easier if you could implement TextWatcher in the holder class and remove the static? That way you can encapsulate the callbacks in the holder. – Jin Sep 29 '16 at 02:54
  • I agree with @JamEngulfer, this is not working because when the EditText is recycled as you are scrolling, it´s value is empty again – Jesus Almaral - Hackaprende Oct 30 '17 at 21:35
  • @JesusAlmaral do you observe `onTextChanged` being invoked as you recycle? – dkarmazi Oct 30 '17 at 23:32
  • Yes, I am debugging and as I am scrolling, it´s being called multiple times when new rows appear – Jesus Almaral - Hackaprende Oct 30 '17 at 23:34
  • gotcha, I didn't observe this back, but something must have changed. What's your device and api level? – dkarmazi Oct 30 '17 at 23:39
  • I feel obliged to share [this post](https://stackoverflow.com/questions/42680906/android-recyclerview-multiple-edittext-change-simultaneously) for people who are experiencing strange behaviour with this implementation. – barnacle.m Oct 15 '18 at 14:44
  • @barnacle.m please feel free to share a sample project that has issues. I'll be happy to look at and debug it. – dkarmazi Oct 16 '18 at 00:51
  • 3
    This solution has a bug. One should never store position information passed to `onBindViewHolder` because the position of an item may change without another `onBindViewHolder` call being made. Instead `MyCustomEditTextListener` should be have a reference to a `ViewHolder` and call `ViewHolder.getAdapterPosition()` to obtain correct position. – Andrey Makarov May 16 '19 at 13:10
  • Hello Team, It's very strange if you add Header & Footer layout this is not working. I tried several way to solve this. I tried to solve this with Header & footer layout . – Zala Janaksinh Jun 24 '20 at 06:13
  • The edittext that is currently focused loses it's original cursor position and resets to start. If I use select selection with setText inside onBindViewHolder() it slows the EditText – user2934930 May 03 '21 at 15:51
24

I had the same problem, I added the following line and it seems to have fixed the problem on my side.

mRecyclerview.setItemViewCacheSize(mDataset.size());

Hopefully this sorts out the issue on your side.

timm_oh
  • 417
  • 4
  • 13
16

I implemented @dkarmazi solution, but it didn't help me. So, I've come further and there's truly working solution.

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        // pass MyCustomEditTextListener to viewholder in onCreateViewHolder
        // so that we don't have to do this expensive allocation in onBindViewHolder
        ViewHolder vh = new ViewHolder(v, new MyCustomEditTextListener());

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        // update MyCustomEditTextListener every time we bind a new item
        // so that it knows what item in mDataset to update
        holder.myCustomEditTextListener.updatePosition(holder.getAdapterPosition());
        holder.mEditText.setText(mDataset[holder.getAdapterPosition()]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }

    @Override
    public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
        ((ViewHolder) holder).enableTextWatcher();
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
        ((ViewHolder) holder).disableTextWatcher();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public EditText mEditText;
        public MyCustomEditTextListener myCustomEditTextListener;

        public ViewHolder(View v, MyCustomEditTextListener myCustomEditTextListener) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);
            this.myCustomEditTextListener = myCustomEditTextListener;
        }
        
        void enableTextWatcher() {
            mEditText.addTextChangedListener(myCustomEditTextListener);
        }

        void disableTextWatcher() {
            mEditText.removeTextChangedListener(myCustomEditTextListener);
        }
    }

    // we make TextWatcher to be aware of the position it currently works with
    // this way, once a new item is attached in onBindViewHolder, it will
    // update current position MyCustomEditTextListener, reference to which is kept by ViewHolder
    private class MyCustomEditTextListener implements TextWatcher {
        private int position;

        public void updatePosition(int position) {
            this.position = position;
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            // no op
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            mDataset[position] = charSequence.toString();
        }

        @Override
        public void afterTextChanged(Editable editable) {
            // no op
        }
    }
}

The main problem was that applied TextWatcher continued to work during item recycling.

I've tried to disable it before recycling, but there's no any "beforeRecycle" event methods. So I used onViewDetachedFromWindow method, and it has worked well.

RomanMitasov
  • 925
  • 9
  • 25
5

I would create an interface and pass the current adapter position to handle the text change event

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        ViewHolder vh = new ViewHolder(v, new ViewHolder.ITextWatcher() {
            @Override
            public void beforeTextChanged(int position, CharSequence s, int start, int count, int after) {
                // do something
            }

            @Override
            public void onTextChanged(int position, CharSequence s, int start, int before, int count) {
                mDataset[position] = s.toString();
            }

            @Override
            public void afterTextChanged(int position, Editable s) {
                // do something
            }
        });

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        holder.mEditText.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }


    public static class ViewHolder extends RecyclerView.ViewHolder {

        public EditText mEditText;
        private ITextWatcher mTextWatcher;

        public interface ITextWatcher {
            // you can add/remove methods as you please, maybe you dont need this much
            void beforeTextChanged(int position, CharSequence s, int start, int count, int after);

            void onTextChanged(int position, CharSequence s, int start, int before, int count);

            void afterTextChanged(int position, Editable s);
        }

        public ViewHolder(View v, ITextWatcher textWatcher) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);

            this.mTextWatcher = textWatcher;

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

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

                @Override
                public void afterTextChanged(Editable s) {
                    mTextWatcher.afterTextChanged(getAdapterPosition(), s);
                }
            });
        }
    }

}
tanjir
  • 1,294
  • 13
  • 22
cpienovi
  • 410
  • 4
  • 7
4

Create a String array with the size of your adapter data.

Eg: String[] texts = new String[dataSize];

on the onBindViewHolder method inside your adapter , add a TextChangedListener to the Textview.

Eg : -

@Override
    public void onBindViewHolder(Viewholder holder, int position) {

//binding data from array 
   holder.yourEditText.setText(texts [position]);
   holder.yourEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                //setting data to array, when changed
                texts [position] = s.toString();
            }

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

            @Override
            public void afterTextChanged(Editable s) {
                //blank
            }
        });


}
J.R
  • 2,113
  • 19
  • 21
  • 1
    Hi @JITHINRAJ without this "addtextChangedListener" my code works fine obviusly not saving the content of the edit Text when scrolled. If i add this code, when i scroll all editText views that go off screen and than come back in have messed up content. – gatteo Aug 06 '15 at 11:29
2

Hi @mikwee make sure you are adding text changed listener in below method rather than adding it to onBindViewHolder().

public ViewHolder(View v) {
        super(v);

  yourEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start,
                int before, int count) {
            //setting data to array, when changed
            texts [position] = s.toString();
        }

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

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });


}
Punit Shah
  • 145
  • 1
  • 6
2

According to me this is more optimize of @dkarmazi's answer

public class UploadPhotoAdapter extends RecyclerView.Adapter<UploadPhotoAdapter.MyViewHolder> {
        ArrayList<Feed> feeds;
        Activity activity;
        public UploadPhotoAdapter(Activity activity, ArrayList<Feed> feeds) {
            this.feeds = feeds;
            this.activity = activity;
        }

        @Override
        public UploadPhotoAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.upload_feeds_recycler, parent, false);
            return new UploadPhotoAdapter.MyViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(final UploadPhotoAdapter.MyViewHolder holder, int position) {
            Feed feed = feeds.get(holder.getAdapterPosition());
            holder.captionEditText.setText(feed.getDescription());
            holder.captionEditText.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) {
                    feeds.get(holder.getAdapterPosition()).setDescription(s.toString());
                }
                @Override
                public void afterTextChanged(Editable s) {}
            });
        }
        @Override
        public int getItemCount() {
            return feeds.size();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {
            EditText captionEditText;
            public MyViewHolder(View view) {
                super(view);
                captionEditText = (EditText) view.findViewById(R.id.captionEditText);
            }
        }

    }
Nandkishor mewara
  • 2,552
  • 16
  • 29
2

Override onViewRecycled method in RecyclerView adapter like this:

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    mDataSet[holder.getAdapterPosition()] = holder.mEditText.getText().toString();
}
Pourqavam
  • 50
  • 1
  • 7
  • Using `holder.getAdapterPosition()` inside the TextWatcher is the best solution - then you do not need to keep track of position, because the recycler is doing it for you. Just make sure that only one TextWatcher is added in the view holders constructor. – zkon Sep 02 '19 at 07:17
  • @zkon My solution don't need TextWatcher. It's just override the onViewRecycled method of the RecyclerView.Adapter class. – Pourqavam Sep 03 '19 at 19:03
  • @Pourqavam The problem with that is that you can edit the text and then press a save button (on the appBar) and `onViewRecycled()` will **not** be called, so you wont pickup the changes. With the `TextWatcher` you will always be notified of changes. – zkon Sep 05 '19 at 01:49
2

The problem is that you 【add】 a listener , not 【set】 a listener. So when view is recycled, some EditText will have listeners more than 1.

To fix this problem, should clear listeners when view recycled.

Step 1

use custom EditText replace.

public class RecyclerViewEditText extends AppCompatEditText {

    private ArrayList<TextWatcher> mListeners = null;

    public RecyclerViewEditText(@NonNull Context context) {
        super(context);
    }

    public RecyclerViewEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RecyclerViewEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void addTextChangedListener(TextWatcher watcher)
    {
        if (mListeners == null)
        {
            mListeners = new ArrayList<>();
        }
        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;
        }
    }
}

step 2

replace EditText with above in xml.

    <com.xxxxxxxx.widget.RecyclerViewEditText
        android:id="@+id/et_remark"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:background="@null"
        android:gravity="end"
        android:hint="input hint"
        android:inputType="textPersonName" />

step 3

when view recycled, clear all listeners in your adapter.

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    holder.et_remark.clearTextChangedListeners();
    super.onViewRecycled(holder);
}

see https://stackoverflow.com/a/6270674/1227525

aotian16
  • 767
  • 1
  • 10
  • 21
2

EditText#setText copies the string into an Editable which can be read by calling getText() right ahead. Thus, you can create an alternative editable data set:

fun onBindViewHolder(…) {
    holder.editText.setText(editables[position] ?: texts[position])
    editables[position] = holder.editText.text
}

Now editables will always hold actual data.

Here's a full sample that I wrote.

starball
  • 20,030
  • 7
  • 43
  • 238
Miha_x64
  • 5,973
  • 1
  • 41
  • 63
1

For me the above solutions didnt work. For some of them, the listener was not calling and when the listener was called in the onBindViewHolder method, it seems even when scrolling the listener events are called. 'Text' is changing, So i tried key listener and it worked fine. No keys are pressed during scrolling i guess.

holder.ticketNo.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    results.get(position).TicketNo = holder.ticketNo.getText().toString();
                    return false;
                }
            });

The code that worked for me.

Sely Lychee
  • 312
  • 1
  • 9
1

I overrided the method getItemViewType to resolved for me

override fun getItemViewType(position: Int): Int {
    return position
}
  • 3
    Unfortunatelly that would create number of view holders per element and they will never be reused. In fact, the performance would be exactly the same as adding new elements inside `LinearLayout` – ruX Feb 12 '20 at 17:55
0

I'm not that familiar with RecyclerView objects, but I had the same issue with ListView. For those ones, I usually create an ad-hoc class representing values inserted into my views (it works with EditTexts, Checkboxes, RadioButtons...) and get updated data through them. I then create a custom ArrayAdapter consisting of said container objects, retrieving values to put into the edittexts at every getView() callback from them and implementing a textwatcher to keep these objects up to date. Again, I don't exactly remember how RecyclerViews work, but if they involve Adapters, this could be a good hack for you to try.

StG
  • 257
  • 2
  • 11
0

use Two-way binding if working with databinding

https://medium.com/swlh/how-data-binding-helps-you-when-working-with-edittext-inside-recyclerview-543a1eb5f2cc

android:text="@={model.name}"

emad pirayesh
  • 654
  • 7
  • 12
0

implements View.OnFocusChangeListener

@Override
public void onBindViewHolder(Viewholder holder, int position) {
        editText.setText(model.getValue());
        editText.setOnFocusChangeListener(this);
        editText.setTag(position);
}

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (v.getTag() == null)
        return;
    int position = (int) v.getTag();
    if (!hasFocus && v instanceof EditText)
        mValues.get(position).setValue(((EditText) v).getText().toString());
}
0

I used the tag property to save past watcher and be able to remove it:

 editText.tag?.let {
      editText.removeTextChangedListener(it as TextWatcher)
 }

object:TextWatcherAdapter() {
       override fun afterTextChanged(s: Editable) {
               // do something with text
       }
}.also { 
       editText.addTextChangedListener(it)
       editText.tag = it
}
            
htafoya
  • 18,261
  • 11
  • 80
  • 104
0

I think this is the easiest solution. Just setOnFocusChangeListener to your EditText and add/remove TextWatcher accordingly:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    // Initialize the TextWatcher when the ViewHolder is bound.
    // You need to make sure you don't add a TextWatcher to an EditText that already has one.

    holder.editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            EditText editText = (EditText) v;

            if (hasFocus) {
                // Add the TextWatcher when the EditText has focus
                TextWatcher textWatcher = 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) {
                        // ...
                    }
                };
                
                editText.addTextChangedListener(textWatcher);
                editText.setTag(textWatcher);  // Store the TextWatcher as a tag so you can remove it later.
            } else {
                // Remove the TextWatcher when the EditText loses focus
                TextWatcher textWatcher = (TextWatcher) editText.getTag();
                if (textWatcher != null) {
                    editText.removeTextChangedListener(textWatcher);
                }
            }
        }
    });

}
M_droid
  • 2,447
  • 2
  • 25
  • 35