1

enter image description here

enter image description here

I am developing the Workout log app now.

Two items (routine and routine detail) are expressed using one recycler view and adapter.

If i click the Add Routine button, a routine item is added, and the routine basically has one routine detail item.

Routine items have buttons to add or delete routine detail items.

I used DiffUtil to update the item.

In areItemsTheSame(), I used hashCode to compare oldList and newList.

However, there is a problem of unknown cause when adding or deleting items. (Not an error).

If i click the delete button after adding the routine item and the detail item, it works well at first.

The routine detail buttons of the next routine item cannot be added or deleted unless the delete button of the previous routine item is pressed.

If the delete button of the previous routine item is pressed and then the button of the next item is pressed, the addition or deletion is performed.

Why is this?

This happens when you use hashCode comparison.

However, with equals() this does not happen and works fine.

Instead, every time an item is added or deleted, the entire item is updated with blinking.

How did I have to define the DiffUtil class?

CODE

RoutineModel.java

public class RoutineModel {
    private ArrayList<RoutineDetailModel> routineDetailList;
    private String routine;
    
    public RoutineModel(String routine) {
        this.routine = routine;
    }

    public void addDetail(RoutineDetailModel item) {
        if(routineDetailList == null) {
            routineDetailList = new ArrayList<>();
        }
        this.routineDetailList.add(item);
    }

    public ArrayList<RoutineDetailModel> getDetailItemList() {
        return routineDetailList;
    }

    public int getDetailItemSize() {
        return routineDetailList.size();
    }

    public String getRoutine() {
        return routine;
    }

    public void removeDetails(int index) throws Exception {
        this.routineDetailList.remove(index);
    }

    @Override
    public int hashCode() {
        return Objects.hash(routineDetailList, routine);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        RoutineModel that = (RoutineModel) obj;
        return Objects.equals(routine, that.routine) && Objects.equals(routineDetailList, that.routineDetailList);
    }
}

RoutineAdapter.java

public class RoutineAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    final static int TYPE_ROUTINE = 1;
    final static int TYPE_ROUTINE_DETAIL = 2;
    private Context context;
    private List<Object> mItems = new ArrayList<>();
    OnRoutineItemClickListener listener;

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

    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object object = mItems.get(position);
        if(object instanceof RoutineModel) {
            setRoutineData((RoutineViewHolder) holder, (RoutineModel) object, position);
        }
        else if(object instanceof RoutineDetailModel) {

        }
    }

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

        holder.addSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(listener != null) listener.OnAddBtnClick(position);
            }
        });

        holder.deleteSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(listener != null) listener.OnDeleteBtnClick(position);
            }
        });
    }
    public Object getRoutineItem(int position) {
        if(mItems == null || position < 0 || position >= mItems.size())
            return null;
        return mItems.get(position);
    }

    @Override
    public int getItemCount() {
        if(mItems == null)
            return -1;
        return mItems.size();
    }


    @Override
    public int getItemViewType(int position) {
        Object obj = mItems.get(position);
        if(obj instanceof RoutineModel) {
            return TYPE_ROUTINE;
        }
        return TYPE_ROUTINE_DETAIL;
    }
    
    // detail add,delete click interface
    public interface OnRoutineItemClickListener {
        public void OnAddBtnClick(int curRoutinePos);
        public void OnDeleteBtnClick(int curRoutinePos);
    }

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

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

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

    public class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        public TextView set;
        public TextView weight;

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

            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);
        }
    }
}

RoutineDiffUtil.java

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

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

     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) {
//         boolean result = oldRoutineList.equals(newRoutineList); // work well
         boolean result = oldRoutineList.get(oldItemPosition).hashCode() == newRoutineList.get(newItemPosition).hashCode();
         return result;
     }

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

MainActivity.java

public class WriteRoutineActivity extends AppCompatActivity {
    Button add_routine_btn;
    TextView title;
    RecyclerView routine_rv;

    LinearLayoutManager routineLayoutManger;
    RoutineAdapter routineAdapter;
    List<RoutineModel> items;
    List<String> titleData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write_routine);

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

        items = new ArrayList<>();
        routineAdapter = new RoutineAdapter();
        routine_rv.setAdapter(routineAdapter);

        add_routine_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                WorkoutListDialogFragment routineDialog = new WorkoutListDialogFragment();
                routineDialog.show(getSupportFragmentManager(), "RoutineListDialog");
            }
        });

        routineAdapter.setOnRoutineClickListener(new RoutineAdapter.OnRoutineItemClickListener() {
            @Override
            public void OnAddBtnClick(int routinePos) {
                Object obj = routineAdapter.getRoutineItem(routinePos);
                if(obj instanceof RoutineModel) {
                    RoutineModel item = (RoutineModel) obj;
                    item.addDetail(new RoutineDetailModel());
                    routineAdapter.updateRoutineList(getDataToBeDisplayed());
                }
            }
            @Override
            public void OnDeleteBtnClick(int routinePos) {
                Object item = routineAdapter.getRoutineItem(routinePos);
                if(item instanceof RoutineModel) {

                    RoutineModel routineModel = (RoutineModel) item;
                    if(routineModel.getDetailItemSize() > 1) {
                        try {
                           routineModel.removeDetails(routineModel.getDetailItemSize() - 1);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    else { // if delete item exists only one
                        items.remove(routineModel);
                    }
                    routineAdapter.updateRoutineList(getDataToBeDisplayed());
                }
            }
        });
    }

    public void addRoutine(String routine) {
        RoutineModel routineModel = new RoutineModel(routine);
        RoutineDetailModel routineDetailModel = new RoutineDetailModel();
        routineModel.addDetail(routineDetailModel);
        items.add(routineModel);
        routineAdapter.updateRoutineList(getDataToBeDisplayed());
    }
    
    private List<Object> getDataToBeDisplayed() {
        List<Object> mixedList = new ArrayList<>();
        for(RoutineModel rm: items){
            mixedList.add(rm);
            if(rm.getDetailItemList() != null && rm.getDetailItemSize() > 0){
                for(RoutineDetailModel rmdetilas: rm.getDetailItemList()){
                    mixedList.add(rmdetilas);
                }
            }
        }
        return mixedList;
    }
}
a_local_nobody
  • 7,947
  • 5
  • 29
  • 51
ybybyb
  • 1,385
  • 1
  • 12
  • 33
  • 1
    Hash code can produces false-positive due to colliding values. It should never be used to check for equality. – Nicolas Feb 11 '21 at 19:31
  • But when someone overrides `equals()`, they said to override `hashCode()` as well. So I also override `hashCode()`, and use this for `areItemsTheSame()` – ybybyb Feb 11 '21 at 19:35
  • 1
    Yes. The reason this is said is because hash code is required for your class to work correctly with container classes like `HashSet` and `HashMap`. If you don't intend to use it as such then there is actually no reason to implement it... but you still should, otherwise in the future you may tend to forget to do so in situations where it is required. It's a good practice, that's all. – Nicolas Feb 11 '21 at 19:43
  • 1
    Nothing forces you to use `hashCode` in `areItemsTheSame`. The contract for `hashCode` is "two objects that are equal will return the same hash code" but nothing says "two objects that are unequal will return a different hash code", which might be the cause of the issue here. – Nicolas Feb 11 '21 at 19:43
  • hmm.. bit difficult...haha.. As you can see I don't use `Hash Class`. If so, according to your answer I don't need to implement `hashCode()` at all? But this is what I asked last time, and the people who responded here told me to override `hashCode()`. This doesn't use `hashCode()`, but is it just telling me to override it for the future? Is your answer also to override for the future?? https://stackoverflow.com/questions/65997615/android-can-i-use-this-code-in-my-diffutill-implementation – ybybyb Feb 11 '21 at 19:54
  • 1
    It's a good practice to always override `equals` and `hashCode` together. See [Effective Java Chapter 3 Item 9](https://github.com/tatsuya/effective-java/blob/0403304667edb2d6dd38864399829bfd5c71957f/chapter-3.md#item-9-always-override-hashcode-when-you-override-equals). A good practice is meant to help you. Think of other good pratices: indenting your code correctly, adding comments, unit testing, etc. These are all things you could *not* do, but for your own sake it's best you do. – Nicolas Feb 11 '21 at 20:02
  • Oh, so when I override `equals()`, I can think of it as overriding `hashCode()` together? That means that `hashCode()` and `equals()` are a bundle? Even if I don't use `hashCode()`? Even if `hashCode()` is not intended to be used, should it be overridden? – ybybyb Feb 11 '21 at 20:08
  • 1
    Yes, exactly. :) – Nicolas Feb 11 '21 at 20:11
  • Okay, thank you, I'll ask one more. So, what should I compare between the two items in `areItemsTheSame()`? `areContentsTheSame()` compares the contents of each item (member variables of each item such as routine name and routine detail list) through overriding of `equals()`. `areItemsTheSame()` is what Should I compare? +++)) and if I use `List` and `multi-view type` like me, should I compare the types of each type as in `onCreateViewHolder()`? – ybybyb Feb 11 '21 at 20:20
  • 1
    Actually `areItemsTheSame` should be the one using `equals`. `areContentsTheSame` should return true only if the items will appear visually different. For example if your item has an ID attribute only used by the database then you wouldn't compare it there. But it can also be the same as `areItemsTheSame` without problem. If items of different type appear differently then yes you should compare it. – Nicolas Feb 11 '21 at 20:24
  • So, in `areItemsTheSame()`, if it's not just the data of the item, it's just comparing the old and new items to see if they are the same, then `oldRoutineList.get(oldItemPosition).equals(newRoutineList.get(newItemPosition))` is this code correct? Is this just comparing the `address value (reference value)` of the `item (object)`? And in `areContentsTheSame()`, is it correct to compare the `data of each item`. (like Routine name, set, lbs..)? – ybybyb Feb 12 '21 at 17:59

0 Answers0