Saving Edittext Content in Recyclerview

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

Sample Image

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



Leave a reply



Submit