0

enter image description here

I have attached a picture to help you understand my problem.

I'm making an Workout app.

When the button is pressed, the routine is added In the added routine, it can use the button again to add detailed data (set, lbs, reps).

So, I am using a two type RecyclerView (except footer).

I used DiffUtil to update the items being added.

I found a strange phenomenon while experimenting for testing. (See photo)

It wasn't an error, so I can't figure out what's wrong with it.

After entering the data, when i added a number of detailed items, the value of the data entered first appeared in the items added later.

And if i scrolled after many items were added, the data moved randomly.

However, what is expected is this phenomenon when you add a lot of items or scroll.

What's wrong?


RoutineAdapter.java

public class RoutineAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    final static int TYPE_ROUTINE = 1;
    final static int TYPE_ROUTINE_DETAIL = 2;
    final static int TYPE_ROUTINE_FOOTER = 3;

    private Context context;
    private List<Object> mItems = new ArrayList<>();
    OnRoutineItemClickListener routinelistener;
    OnRoutineAddClickListener routineAddListener;

    public void updateRoutineList(List<Object> newRoutineList) {
        final RoutineDiffUtil diffCallback = new RoutineDiffUtil(this.mItems, newRoutineList);
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);

        this.mItems.clear();
        this.mItems.addAll(newRoutineList);
        diffResult.dispatchUpdatesTo(this);
    }

    @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 RoutineViewHolder(itemView);
        }
        else if(viewType == TYPE_ROUTINE_DETAIL){
            itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
            return new RoutineDetailViewHolder(itemView);
        }
        else {
            itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
            return new RoutineAddFooterViewHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object obj;
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                obj = mItems.get(position);
                setRoutineData((RoutineViewHolder) holder, (RoutineModel) obj);
                break;
            case TYPE_ROUTINE_DETAIL:
                obj = mItems.get(position);
                RoutineDetailModel item = (RoutineDetailModel) obj;
                ((RoutineDetailViewHolder) holder).setDetailItem(item);
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }
    
    @Override
    public int getItemCount() {
        if(mItems == null)
            return -1;
        return mItems.size() + 1; // for footer
    }

    @Override
    public int getItemViewType(int position) {
        if(position == mItems.size()) {
            return TYPE_ROUTINE_FOOTER;
        }
        else {
            Object obj = mItems.get(position);
            if(obj instanceof RoutineModel) {
                return TYPE_ROUTINE;
            }
            else {
                // obj instanceof RoutineDetailModel
                return TYPE_ROUTINE_DETAIL;
            }
        }
    }
    
    // add routine interface
    public interface OnRoutineAddClickListener {
        public void onAddRoutineClick();
    }

    public void setOnAddRoutineClickListener(OnRoutineAddClickListener listener) {
        this.routineAddListener = listener;
    }
    
    // details add / remove interface
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
    }

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

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

            initViews();

            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 {
        public TextView set;
        public TextView weight;

        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            initViews();
        }

        private void initViews() {
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);
        }

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

RoutineDiffUtil.java

public class RoutineDiffUtil extends DiffUtil.Callback {
    private List<Object> oldRoutineList;
    private List<Object> newRoutineList;

    public RoutineDiffUtil(List<Object> oldRoutineList, List<Object> newRoutineList) {
        this.oldRoutineList = oldRoutineList;
        this.newRoutineList = newRoutineList;
    }

    @Override
    public int getOldListSize() {
        return oldRoutineList.size();
    }

    @Override
    public int getNewListSize() {
         return newRoutineList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        Object oldObj = oldRoutineList.get(oldItemPosition);
        Object newObj = newRoutineList.get(newItemPosition);
        if (oldObj instanceof RoutineModel && newObj instanceof RoutineModel) {
            return ((RoutineModel) oldObj).id == ((RoutineModel) newObj).id;
        }
        else if (oldObj instanceof RoutineDetailModel && newObj instanceof RoutineDetailModel) {
            return ((RoutineDetailModel) oldObj).id == ((RoutineDetailModel) newObj).id;
        }
        else if(oldObj instanceof RoutineModel && newObj instanceof RoutineDetailModel) {
            return false;
        }
        else {
            return false;
        }
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return (oldRoutineList.get(oldItemPosition)).equals(newRoutineList.get(newItemPosition));
    }
}

RoutineDetailModel.java

public class RoutineDetailModel {
    public int id;
    private int set = 1;
    private int weight;

    public RoutineDetailModel() {
        Random random = new Random();
        this.id = random.nextInt();
    }

    public RoutineDetailModel(int set) {
        Random random = new Random();
        this.id = random.nextInt();
        this.set = set+1;
    }
    public Integer getSet() {
        return set;
    }

    public int getId() {
        return id;
    }
    @Override
    public int hashCode() {
        return Objects.hash(set, weight);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if(obj != null && obj instanceof RoutineDetailModel) {
            RoutineDetailModel model = (RoutineDetailModel) obj;
            if(this.id == model.getId()) {
                return true;
            }
        }
        return false;
    }
}
ybybyb
  • 1,385
  • 1
  • 12
  • 33

1 Answers1

2

First of all, you should use ListAdapter class with diff util. The problem with your adapter is that, Recycler view recycles views again and again. That is to say that when you entered a text for your first item, this item is used for other views. To solve it after any text change you should keep this text in your model class then you should set this text to the field in onBind() method. To sum up, recycler view uses same view for different items so any data related to any item should be kept in a model and the model should be set to the view in onBind().

M.ekici
  • 765
  • 4
  • 10