By gatteo


2015-08-05 23:18:53 8 Comments

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.

11 comments

@Sely Lychee 2019-08-22 16:53:25

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.

@Pourqavam 2019-01-24 21:58:17

Override onViewRecycled method in RecyclerView adapter like this:

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    mDataSet[holder.getAdapterPosition()] = holder.mEditText.getText().toString();
}

@zkon 2019-09-02 07:17:25

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.

@Pourqavam 2019-09-03 19:03:01

@zkon My solution don't need TextWatcher. It's just override the onViewRecycled method of the RecyclerView.Adapter class.

@zkon 2019-09-05 01:49:51

@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.

@RomanMitasov 2019-01-07 17:24: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.

There's only one problem, after adding to EditText custom Editable.Factory the list brakes again. I don't know why, maybe I will find the solution for this issue too

@kishu mewara 2018-03-10 05:15:42

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);
            }
        }

    }

@vishnuc156 2017-12-20 15:11:35

Just override below method to solve the issue:

@Override
public int getItemViewType(final int position) {
    return position;
}

@J.R 2015-08-06 03:53:47

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
            }
        });


}

@gatteo 2015-08-06 11:29:18

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.

@dkarmazi 2015-08-06 15:54:07

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
        }
    }
}

@gatteo 2015-08-07 13:12:24

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

@dkarmazi 2015-08-07 13:24:05

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

@Varun Agarwal 2015-10-09 17:23:05

Hi, this solution works well, but what if I have more than one edit text in each row of the recycler view?

@dkarmazi 2015-10-11 11:33:25

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.

@Doug Ray 2016-02-25 01:41:18

hmmm solution didn't work for me :( still erases edit text when I scroll down.

@dkarmazi 2016-02-27 17:29:27

@DougRay, post your code or link to it, it's hard to tell what goes wrong without seeing the code.

@quangson91 2016-03-09 02:47:16

@dkarmazi Why no need call notifyItemChanged(index); when we update content of adapter ?

@dkarmazi 2016-03-09 05:08:25

@quangson91, I'm not sure if I completely understand the question. Could you please point to where specifically we update the content of adapter?

@quangson91 2016-03-09 05:45:54

@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

@dkarmazi 2016-03-09 16:15:19

@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.

@quangson91 2016-03-09 16:22:22

Ok, So if content visible is same mDateset then we no need call notify to adapter

@dkarmazi 2016-03-10 04:50:27

@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

@quangson91 2016-03-10 06:10:24

@dkarmazi Ok, So for another case we must call notifyItemChange.

@ARUNBALAN NV 2016-05-31 05:48:28

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?

@dkarmazi 2016-05-31 15:41:03

@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?

@Yogesh Suthar 2016-06-11 12:43:05

+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.

@Ninja Coding 2016-06-17 14:04:06

UPDATE: should use holder.getAdapterPosition() instead of position in: holder.myCustomEditTextListener.updatePosition(position);

@JamEngulfer 2016-07-25 01:51: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

@dkarmazi 2016-07-27 15:47:36

@Kuriel, great catch, I edited the answer

@Jin 2016-09-29 02:54:37

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.

@Jesus Almaral 2017-10-30 21:35:58

I agree with @JamEngulfer, this is not working because when the EditText is recycled as you are scrolling, it´s value is empty again

@dkarmazi 2017-10-30 23:32:49

@JesusAlmaral do you observe onTextChanged being invoked as you recycle?

@Jesus Almaral 2017-10-30 23:34:19

Yes, I am debugging and as I am scrolling, it´s being called multiple times when new rows appear

@dkarmazi 2017-10-30 23:39:47

gotcha, I didn't observe this back, but something must have changed. What's your device and api level?

@barnacle.m 2018-10-15 14:44:44

I feel obliged to share this post for people who are experiencing strange behaviour with this implementation.

@dkarmazi 2018-10-16 00:51:39

@barnacle.m please feel free to share a sample project that has issues. I'll be happy to look at and debug it.

@Andrey Makarov 2019-05-16 13:10:54

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.

@timm_oh 2016-07-14 09:12:05

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.

@Scott Biggs 2016-08-01 01:47:15

This will work, but defeats the whole purpose of "recycling views" in a RecyclerView.

@Mohsin 2016-08-10 11:30:31

perfect for small set of data

@Stuti Kasliwal 2018-07-23 10:56:26

Really helpful!! after so much of struggle finally done with this answer. (y)

@cpienovi 2016-02-23 16:38:47

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);
                }
            });
        }
    }

}

@Punit Shah 2016-01-12 19:19:21

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

        }
    });


}

@StG 2015-08-06 00:03:29

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.

Related Questions

Sponsored Content

20 Answered Questions

[SOLVED] What's the best way to limit text length of EditText in Android

31 Answered Questions

52 Answered Questions

44 Answered Questions

[SOLVED] RecyclerView onClick

24 Answered Questions

[SOLVED] Place cursor at the end of text in EditText

2 Answered Questions

17 Answered Questions

[SOLVED] How to create RecyclerView with multiple view type?

39 Answered Questions

[SOLVED] How to add dividers and spaces between items in RecyclerView?

Sponsored Content