Saving EditText content in RecyclerView
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
}
}
}
How to save data from RecyclerView EditText
It is not so hard to save data with SharedPreferences
. But, it does take a few lines of code. So, I'd prefer you to use my library. It is even faster to use. You can add it to your app using the README.md
file. Now, I can give you the entire adapter code with that functionality below:
public class ExercisesRecyclerView extends RecyclerView.Adapter<ExercisesRecyclerView.MyViewHolder>
{
String[] data1, data2, videoURL;
int[] images;
Context context;
TinyDBManager tinyDB; // added this line
public ExercisesRecyclerView(Context ct, String[] s1, String[] s2, String[] videoArray) {
context = ct;
data1 = s1;
data2 = s2;
videoURL = videoArray;
tinyDB = TinyDB.getInstance(context); // added this line
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.exercises_row, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.level.setText(data1[position]);
holder.description.setText(data2[position]);
holder.playVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
YouTubePlayerView youTubePlayerView;
AlertDialog.Builder dialogBuilder;
AlertDialog dialog;
dialogBuilder = new AlertDialog.Builder(context);
LayoutInflater inflater = LayoutInflater.from(context);
View view2 = inflater.inflate(R.layout.popup,null);
youTubePlayerView = view2.findViewById(R.id.youtube_player_view);
youTubePlayerView.addYouTubePlayerListener(new AbstractYouTubePlayerListener() {
@Override
public void onReady(@NonNull YouTubePlayer youTubePlayer) {
String videoID = videoURL[position];
youTubePlayer.loadVideo(videoID,0);
super.onReady(youTubePlayer);
}
});
dialogBuilder.setView(view2);
dialog = dialogBuilder.create();
dialog.show();
}
});
}
@Override
public int getItemCount() {
return data2.length;
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView level, description;
ImageView playVideoButton;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
level = itemView.findViewById(R.id.exercisesLevelTextView);
description = itemView.findViewById(R.id.exercisesNameTextView);
playVideoButton = itemView.findViewById(R.id.playVideoButton);
}
}
}
That does the init part for the db. But, because I don't find your edit text anywhere in the adapter, I have not added that part. But, the code for that edit text will be like this:
edittext.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
tinyDB.putString("value", s.toString());
}
});
But, dont forget to change the key in that place according your requirements.
Saving the values entered into EditTexts of a RecyclerView
If you take for example the following RecyclerView.Adapter
implementation, you'll notice a few things. The List
containing the data is static
meaning it will not be reset on orientation changes. We are adding a TextWatcher
to the EditText
to allow us to update the values when they are modified, and also we are keeping track of the position by adding a tag to the view. Note that this is my particular approach, there are many ways of solving this.
Demo
Adapter
public class SampleAdapter extends RecyclerView.Adapter{
private static List<String> mEditTextValues = new ArrayList<>();
public SampleAdapter(){
//Mocking content for the editText
for(int i=1;i<=10;i++){
mEditTextValues.add("I'm editText number "+i);
}
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.edittext,parent,false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CustomViewHolder viewHolder = ((CustomViewHolder)holder);
viewHolder.mEditText.setTag(position);
viewHolder.mEditText.setText(mEditTextValues.get(position));
}
@Override
public int getItemCount() {
return mEditTextValues.size();
}
public class CustomViewHolder extends RecyclerView.ViewHolder{
private EditText mEditText;
public CustomViewHolder(View itemView) {
super(itemView);
mEditText = (EditText)itemView.findViewById(R.id.editText);
mEditText.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void afterTextChanged(Editable editable) {}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if(mEditText.getTag()!=null){
mEditTextValues.set((int)mEditText.getTag(),charSequence.toString());
}
}
});
}
}
}
(gif may look like it's being reset, since it's in a loop, it's not)
EditText losing its content on RecyclerView. How to restore its content?
A ViewAdapter
re-uses a View
. So, when scrolling, instead of creating new views, an Adapter will re-use a view that is no longer visible.
So, that is the main reason you have the onBindViewHolder
. With that method, Android gives you chance to update the view content before it becomes visible. If you won't update all contents of the (the TextView
and the EditText
), you observe issues like that one that you mentioned.
Note this code:
@Override
public void onBindViewHolder(@NonNull Myholder myholder, int i) {
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
}
As you can see, you are only updating the TextView
. So, that TextView
(re-used or not) will always display proper text. However, you are not updating the content of the EditText
. This way, if the View was just created, the EditText
is empty. If the view is being re-used, you will see the old value (from a different line).
So, in order to keep update the EditText
properly, you must store all texts in a separated list. For example:
public class AdapterJantri extends RecyclerView.Adapter<AdapterJantri.Myholder> {
private String[] mTextList;
public AdapterJantri() {
mTextList = new String[getItemCount()];
}
@Override
public void onBindViewHolder(@NonNull Myholder myholder, int i) {
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
// Here, you update the EditText content
myholder.edJantriNumber.setText(mTextList[i]);
}
@Override
public void onViewRecycled(@NonNull final Myholder holder) {
// Here, you save the text before the view gets recycled
int index = holder.getAdapterPosition();
if (index >= 0) {
mTextList[index] = holder.edJantriNumber.getText().toString();
}
}
}
EDIT
If you have a TextWatcher
and you need to disable it temporarily, I suggest to:
First
Don't create a TextWatcher
inside the bindViewHolder
. You don't need to create a new one everytime. Just add it and remove it. You can use your own MyHolder
as a TextWatcher
public class Myholder extends RecyclerView.ViewHolder implements TextWatcher {
EditText edJantriNumber;
TextView txtJantriNumber;
public Myholder(@NonNull View itemView) {
super(itemView);
edJantriNumber = itemView.findViewById(R.id.ed_jantri_number);
txtJantriNumber = itemView.findViewById(R.id.txt_jantri_number);
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
// Add your code here
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before,
final int count) {
// Add your code here
}
@Override
public void afterTextChanged(final Editable s) {
// Add your code here
}
public void disableTextChangeListner() {
// This will remove the listener. So, it will stop to listen to new events
edJantriNumber.removeTextChangedListener(this);
}
public void addTextChangeListner() {
// This will add the listener. So, it will start to listen to new events
edJantriNumber.addTextChangedListener(this);
}
}
Then, during bindViewHolder
, disable the listener, set the new text, and add the listener again as foolows:
@Override
public void onBindViewHolder(@NonNull Myholder myholder, int i) {
myholder.disableTextChangeListner();
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
myholder.edJantriNumber.setText(mTextList[i]);
myholder.addTextChangeListner();
}
save all the data of a recyclerview made of edittexts, with a single button
Based on what you describe, I would recommend you just add a text change listener to the EditText in each row so any time the text is updated, the stored array is also updated immediately. This way you don't need any save buttons at all, nor do you need a separate array.
That would look something like this:
public class adapter extends RecyclerView.Adapter<adapter.ViewHolder>{
// Only need one array, it is updated as values are edited
private ArrayList<String> listDatos;
public adapter(ArrayList<String> listDatos) {
this.listDatos = listDatos;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.itemlist,null,false);
ViewHolder holder = new ViewHolder(view);
// add a watcher so any time the value in "nombre" is changed,
// it updates the value stored in the array at the current
// row's position immediately
holder.nombre.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {
listDatos.set(getAdapterPosition(), s.toString());
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.nombre.setText(listDatos.get(position));
}
@Override
public int getItemCount() {
return listDatos.size();
}
// getInput can just return that stored array to get the
// current state at any point (no save buttons needed)
public ArrayList<String> getInput() {
return listDatos;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
EditText nombre;
public ViewHolder(View itemView) {
super(itemView);
nombre = itemView.findViewById(R.id.nombre);
}
}
}
Related Topics
How to Use Disk Caching in Picasso
Android Starting Service At Boot Time , How to Restart Service Class After Device Reboot
How to Install Trusted Ca Certificate on Android Device
Using Facebook Sdk in Android Studio
Soft Keyboard Open and Close Listener in an Activity in Android
How to Change the Background Color of Action Bar'S Option Menu in Android 4.2
How to Layout Text to Flow Around an Image
How to Start Activity in Another Application
Error Inflating Class Android.Support.Design.Widget.Navigationview
How to Pass a Value from One Activity to Another in Android
Disable Scrolling in All Mobile Devices
Android Deprecated Apache Module (Httpclient, Httpresponse, etc.)
Differencebetween Min Sdk Version/Target Sdk Version VS. Compile Sdk Version
Failed to Load Appcompat Actionbar with Unknown Error in Android Studio
Trying to Attach a File from Sd Card to Email
Runtimeexception: Unable to Instantiate Application
Android.View.Inflateexception: Binary Xml File Line #12: Error Inflating Class ≪Unknown≫