1

Inside my RecyclerView Adapter class I have 2 view types to display the results of my query:

 @Query("SELECT l.log_id, l.junction_id ,l.date, l.workout_id, l.total_weight_lifted,
         l.reps, l.set_number FROM log_entries_table 
         AS l LEFT JOIN exercise_workout_junction_table AS ej 
         ON ej.exercise_workout_id = l.junction_id WHERE ej.exercise_id = :exerciseID 
         ORDER BY substr(l.date, -4) DESC, substr(l.date, -7) DESC, (l.date) DESC")

    LiveData<List<Log_Entries>> getAllExerciseHistoryLogs(int exerciseID);

The first view type is used to display all logEntries in which the date is unique: 1

The second view type is to display the rest of the logEntries which share the same date as the above:

2

My current code works fine, however every time I scroll down and the recyclerView updates, all the log-Entries with 'unique' dates (which should use the first viewType) get changed to display the second view type.

How can I stop my recyclerView view type from changing?

Before scroll -> After Scroll

RecyclerView Adapter


public class ExerciseHistoryAdapter2 extends RecyclerView.Adapter {

    private OnItemClickListener listener;
    private List<Log_Entries> allLogEntries = new ArrayList<>();
    private List<String> uniqueDates = new ArrayList<>();
    String logEntryDate;

    public void setExercises(List<Log_Entries> allLogEntries) {
        this.allLogEntries = allLogEntries;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        View view;

        if (viewType == 0) {
            view = layoutInflater.inflate(R.layout.exercise_history_item, parent, false);
            return new ViewHolderOne(view);
        }

        view = layoutInflater.inflate(R.layout.exercise_history_item_two, parent, false);
        return new ViewHolderTwo(view);
    }

    @Override
    public long getItemId(int position) {

        return allLogEntries.get(position).getLog_id();
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

        logEntryDate = allLogEntries.get(position).getDate();

        if (uniqueDates.contains(logEntryDate)) {
            // bindViewHolder2
            ViewHolderTwo viewHolderTwo = (ViewHolderTwo) holder;
            viewHolderTwo.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
            viewHolderTwo.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));

        } else {
            uniqueDates.add(logEntryDate);
            //bind viewholder1
            ViewHolderOne viewHolderOne = (ViewHolderOne) holder;
            viewHolderOne.textViewDate.setText(allLogEntries.get(position).getDate());
            viewHolderOne.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
            viewHolderOne.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));
        }
    }

    @Override
    public int getItemCount() {
        return allLogEntries.size();
    }

 @Override
    public int getItemViewType(int position) {

        logEntryDate = allLogEntries.get(position).getDate();

        if (uniqueDates.contains(logEntryDate)) {
            return 1;
        }
        return 0;
    }

    class ViewHolderOne extends RecyclerView.ViewHolder {
        private TextView textViewDate;
        private TextView textViewWeight;
        private TextView textViewReps;

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

            textViewDate = itemView.findViewById(R.id.textView_dateH);
            textViewWeight = itemView.findViewById(R.id.textView_weightH);
            textViewReps = itemView.findViewById(R.id.textView_repss);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    if (listener != null && position != RecyclerView.NO_POSITION) {
                        listener.onItemClick(allLogEntries.get(position));
                    }
                }
            });
        }
    }

    class ViewHolderTwo extends RecyclerView.ViewHolder {
        private TextView textViewWeight;
        private TextView textViewReps;

        public ViewHolderTwo(@NonNull View itemView) {
            super(itemView);
            textViewWeight = itemView.findViewById(R.id.textView_weightH2);
            textViewReps = itemView.findViewById(R.id.textView_repss2);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    if (listener != null && position != RecyclerView.NO_POSITION) {
                        listener.onItemClick(allLogEntries.get(position));
                    }
                }
            });

        }
    }

    public interface OnItemClickListener {
        void onItemClick(Log_Entries log_entries);
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }
}
Josh Brett
  • 77
  • 3
  • 18

2 Answers2

1

I think to set holder.setIsRecyclable(false); would solve the issue, because the recycler view will then no longer recycle the items... But this is not a good solution for long lists.

EDIT: I reviewd your code in onBindViewHolder()... I think the problem comes with uniqueDates.add(logEntryDate); and that the onBindViewHolder method is called multiple times.

This is how the recycler view proceeds:

  1. the first item in list will be unique because uniqueDates is empty. Therefore it will be added to the list.
  2. the other items will be added correctly, as you see in your first screenshot
  3. when you scroll down, the onBindViewHolder method will be executed for every item again
  4. because the uniqueDates list already contains the first date, as it was added in step one, this item will now recognized as not-unique one
  5. the wrong list will be displayed after scrolling as you see in your second screenshot

SOLUTION: You will have to add a logic which identifies unique dates in another way, which is independet of the onBindViewholder method

OR

you would have to add code, that removes dates on a specific point, so that the list identifies the first item every time as unique and not just the first time.

JonasPTFL
  • 189
  • 4
  • 15
1

Your getItemViewType and onBindViewHolder has some issues.

@Override
public int getItemViewType(int position) {
    // type 0 = with date header
    // type 1 = without date header

    // if list is sorted chronologically
    if (position == 0) {
        return 0
    }

    String currentDate = allLogEntries.get(position).getDate();
    String previousDate = allLogEntries.get(position - 1).getDate();

    if (currentDate.equals(previousDate)) {
        return 1
    } else {
        return 0
    }
}

The first item will always have the header since the list is sorted chronologically. For the rest of the items, you need to check whether the date for the current item is the same as the previous item. Based on that condition you return the type.

You do not need to manage a list of unique dates. You have shared mutable states between the functions that are being called multiple times and are not synced. Just delete these and the references of them from onBindViewHolder

private List<String> uniqueDates = new ArrayList<>();
String logEntryDate;
denvercoder9
  • 2,979
  • 3
  • 28
  • 41