Search code examples
androidandroid-recyclerviewandroid-listadapter

Why do values ​disappear after scrolling in Recycler View?


enter image description here

Data before scrolling enter image description here

Data after scrolling enter image description here


The problem with my app is shown in the pictures above.

After entering the data, if i scroll after adding the item as scrollable, the data disappears.

As a further explanation, sometimes the entered data appears in other items that have been added.

To explain the app, it is an exercise recording app, and it uses a multi-type recycler view.

I used ListAdapter and also DiffUtil. And the picture is related to the Detail item.

TextWatcher was used to save the entered data.

I've been searching a lot to solve this.

The two most searched solutions are here

  1. using getItemViewType(), getItemId() ->I used this method as shown in this link, but it didn't solve the problem.

  2. Using setIsRecyclable(false) inside the holder -> This method worked. But I heard that setIsRecyclable(false) is a function that does not recycle. If i use this, isn't it a good way to do it because there is no advantage to using RecyclerView?

RoutineAdapter.java

public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {
    Context context;
    RoutineListAdapter.OnRoutineItemClickListener routinelistener;
    RoutineListAdapter.OnRoutineAddClickListener routineAddListener;

    final static int TYPE_ROUTINE = 1;
    final static int TYPE_ROUTINE_DETAIL = 2;
    final static int TYPE_ROUTINE_FOOTER = 3;

    public RoutineListAdapter(@NonNull DiffUtil.ItemCallback<Object> diffCallback) {
        super(diffCallback);
    }

    // add routine interface
    public interface OnRoutineAddClickListener {
        public void onAddRoutineClick();
    }

    public void setOnAddRoutineClickListener(RoutineListAdapter.OnRoutineAddClickListener listener) {
        this.routineAddListener = listener;
    }

    // add/remove detail interface
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
    }

    public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
        this.routinelistener = listener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        View itemView;
        if(viewType == TYPE_ROUTINE){
            itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
            return new RoutineListAdapter.RoutineViewHolder(itemView);
        }
        else if(viewType == TYPE_ROUTINE_DETAIL){
            itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
            return new RoutineListAdapter.RoutineDetailViewHolder(itemView);
        }
        else {
            itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
            return new RoutineListAdapter.RoutineAddFooterViewHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object curItem;
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                curItem = getItem(position);
                setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
                break;
            case TYPE_ROUTINE_DETAIL:
                curItem = getItem(position);
                RoutineDetailModel item = (RoutineDetailModel) curItem;
                ((RoutineListAdapter.RoutineDetailViewHolder) holder).bind(item);
                ((RoutineDetailViewHolder) holder).weight.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) {
                        item.setWeight(((RoutineDetailViewHolder) holder).weight.getText().toString());
                    }
                });
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }

    private void setRoutineData(RoutineListAdapter.RoutineViewHolder holder, RoutineModel routineItem){
        holder.routine.setText(routineItem.getRoutine());
    }

    public Object getRoutineItem(int position) {
        if(getCurrentList() == null || position < 0 || position >= getCurrentList().size())
            return null;
        return getItem(position);
    }

    @Override
    public int getItemCount() {
        return getCurrentList().size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if(position == getCurrentList().size()) {
            return TYPE_ROUTINE_FOOTER;
        }
        else {
            Object obj = getItem(position);
            if(obj instanceof RoutineModel) {
                return TYPE_ROUTINE;
            }
            else {
                // obj instanceof RoutineDetailModel
                return TYPE_ROUTINE_DETAIL;
            }
        }
    }

    private class RoutineViewHolder extends RecyclerView.ViewHolder {
        public TextView routine;
        public Button addSet;
        public Button deleteSet;
        public Button comment;

        public RoutineViewHolder(@NonNull View itemView) {
            super(itemView);
            routine = itemView.findViewById(R.id.routine);
            addSet = itemView.findViewById(R.id.add_set);
            deleteSet = itemView.findViewById(R.id.delete_set);
            comment = itemView.findViewById(R.id.write_comment);

            addSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onAddBtnClicked(getAdapterPosition());
                }
            });

            deleteSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onDeleteBtnClicked(getAdapterPosition());
                }
            });

            comment.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
                }
            });
        }
    }

    private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        private TextView set;
        private EditText weight;

        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);
        }

        private void bind(RoutineDetailModel item) {
            set.setText(item.getSet().toString() + "set");
            weight.setText(item.getWeight());
        }
    }

    private class RoutineAddFooterViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public RoutineAddFooterViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.add_text);
            ConstraintLayout regionForClick = itemView.findViewById(R.id.clickable_layout);
            regionForClick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (routineAddListener != null) {
                        routineAddListener.onAddRoutineClick();
                    }
                }
            });
        }
    }
}

UPDATED

Adapter

public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {

//  detail add / remove iterface
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
        public void onWritingWeight(int curRoutinePos, View view); // write weight
    }

    public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
        if(this.routinelistener != null)
            this.routinelistener = null;
        this.routinelistener = listener;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object curItem;
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                curItem = getItem(position);
                setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
                break;
            case TYPE_ROUTINE_DETAIL:
                ((RoutineListAdapter.RoutineDetailViewHolder) holder).bind();
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }

 private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        private TextView set;
        private EditText weight;

        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);

            weight.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) {
                  routinelistener.onWritingWeight(getAdapterPosition(), itemView);
                }
            });
        }

        private void bind() {
            RoutineDetailModel item = (RoutineDetailModel) getItem(getAdapterPosition());
            set.setText(item.getSet().toString() + "set");
            weight.setText(item.getWeight()); // Setting the saved value
        }
    }

Activity

public class WriteRoutineActivity extends AppCompatActivity implements WritingCommentDialogFragment.OnDialogClosedListener {
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write_routine);

        initViews();
        setPageTitle(getIntent());
        setRoutineRecyclerview();

        diffUtil2 = new RoutineDiffUtil2();
        listAdapter = new RoutineListAdapter(diffUtil2);
        items = new ArrayList<>();
        routine_rv.setAdapter(listAdapter);

        listAdapter.setOnRoutineClickListener(new RoutineListAdapter.OnRoutineItemClickListener() {
            @Override
            public void onWritingWeight(int curRoutinePos, View v) {
                RoutineDetailModel item = (RoutineDetailModel) listAdapter.getRoutineItem(curRoutinePos);
                EditText weight = v.findViewById(R.id.weight);
                item.setWeight(weight.getText().toString()); // This is saved to set the value again when recycled.
        });
    }
}

If you need any other extra code, please tell me


Solution

  • The issue lies with your TextWatcher that you're adding in onBindViewHolder.

    At the moment you have it setup so that every time the RecyclerView binds a view (which can happen multiple times per actual view), you're adding a new TextWatcher and then also setting the text to the item's weight, which is then triggering the previous watchers you added, setting the item's weight to something else, in this case an empty string.

    What you should be doing is either removing all listeners before you add another one, or add the listener in onCreateViewHolder and use the holder's adapter position to get your item.


    Here is some pseudocode to clarify my suggestions:

    Adding the listener in onCreateViewHolder

    RoutineDetailViewHolder {
        private EditText weight;
    
        RoutineDetailViewHolder {
    
            weight.addTextChangedListener {
                
                items[adapterPosition].setWeight(...)
            }
        }
    }
    

    Removing the listeners before binding again:

    RoutineDetailViewHolder {
        private EditText weight;
        private TextWatcher weightWatcher;
    
        void bind() {
        
            weight.removeTextChangedListener(weightWatcher);
            
            weightWatcher = new TextWatcher();
            weight.addOnTextChangedListener(weightWatcher);
        }
    }