I've got a RecyclerView
with a dynamic number of tiles. Data is loaded from the server and displayed in these tiles. Glide is used to load images into an avatar on these tiles. Issue is that every time I refresh the list, or add more items to it, it sometimes loads wrong images for the tiles.
For example one user on tile 1 will have a blank avatar, same user on the next tile will have a different avatar (another user's). If I refresh the list, it might become correct, or even more messed up (a third random avatar from some user appears).
What am I doing wrong in my Adapter
? Sharing code below.
public class BookingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
{
private final Context context;
private final Util util;
private final List<Booking> bookings;
private static final int TYPE_BOOKING = 0;
private static final int TYPE_LOADING = 1;
private OnLoadMoreListener loadMoreListener;
private boolean isMoreDataAvailable = true, isLoading = false;
public BookingAdapter(Context context, List<Booking> bookings)
{
this.context = context;
this.bookings = bookings;
util = new Util();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
if(viewType == TYPE_BOOKING)
{
return new BookingViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_booking, parent, false));
}
else
{
return new LoadingViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_load, parent, false));
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
{
if (position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null)
{
isLoading = true;
loadMoreListener.onLoadMore();
}
if (getItemViewType(position) == TYPE_BOOKING)
{
((BookingViewHolder) holder).bindData(bookings.get(position));
}
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public int getItemViewType(int position)
{
if (bookings.get(position).getCreatedAt().equalsIgnoreCase("loading"))
{
return TYPE_LOADING;
}
else
{
return TYPE_BOOKING;
}
}
@Override
public int getItemCount()
{
return bookings != null ? bookings.size() : 0;
}
public class BookingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
private final CircularImageView avatar;
private final ImageView paymentStatusIcon;
private final TextView name;
private final TextView charges;
private final TextView timeSlot;
private final TextView status;
private final TextView cancel;
private final TextView dayOfWeek;
private final TextView dayOfMonth;
private final TextView month;
private Booking currentBooking = null;
private Appointment activeAppointment = null;
public BookingViewHolder(@NonNull View itemView)
{
super(itemView);
avatar = itemView.findViewById(R.id.avatar);
paymentStatusIcon = itemView.findViewById(R.id.paymentStatusIcon);
name = itemView.findViewById(R.id.name);
charges = itemView.findViewById(R.id.charges);
timeSlot = itemView.findViewById(R.id.timeslot);
status = itemView.findViewById(R.id.type);
cancel = itemView.findViewById(R.id.cancel);
dayOfWeek = itemView.findViewById(R.id.dayOfWeek);
dayOfMonth = itemView.findViewById(R.id.dayOfMonth);
month = itemView.findViewById(R.id.month);
cancel.setOnClickListener(this);
itemView.setOnClickListener(this);
}
void bindData(Booking booking)
{
this.currentBooking = booking;
String statusBooking = currentBooking.getStatus();
switch (statusBooking)
{
case Constants.JOB_STATUS_VISIT_PENDING:
case Constants.JOB_STATUS_CANCELLED:
case Constants.JOB_STATUS_OFFER_PENDING:
case Constants.JOB_STATUS_PENDING_CLIENT_APPROVAL:
this.activeAppointment = booking.getVisitAppointment();
break;
case Constants.JOB_STATUS_SCHEDULED:
case Constants.JOB_STATUS_IN_PROGRESS:
case Constants.JOB_STATUS_COMPLETED:
this.activeAppointment = booking.getServiceAppointment();
break;
default:
this.activeAppointment = new Appointment();
break;
}
if(activeAppointment.getAppointmentType().equalsIgnoreCase(Constants.APPOINTMENT_TYPE_SERVICE) && activeAppointment.getStatus().equalsIgnoreCase(Constants.JOB_STATUS_COMPLETED))
{
paymentStatusIcon.setVisibility(View.VISIBLE);
if(activeAppointment.getPaymentStatus().equalsIgnoreCase(Constants.PAYMENT_STATUS_PAID))
{
paymentStatusIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.vector_cash_check));
}
else if(activeAppointment.getPaymentStatus().equalsIgnoreCase(Constants.PAYMENT_STATUS_DECLINED))
{
paymentStatusIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.vector_cash_remove));
}
else
{
paymentStatusIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.vector_cash));
paymentStatusIcon.setImageTintList(ColorStateList.valueOf(ContextCompat.getColor(context, R.color.quantum_amber800)));
}
}
else
{
paymentStatusIcon.setVisibility(View.GONE);
}
if (statusBooking.equalsIgnoreCase(Constants.JOB_STATUS_UNASSIGNED) || statusBooking.equalsIgnoreCase(Constants.JOB_STATUS_PENDING))
{
avatar.setBackground(ContextCompat.getDrawable(context, R.drawable.vector_portrait_placeholder));
name.setText(context.getString(R.string.not_assigned_yet));
status.setText(booking.getStatus());
timeSlot.setVisibility(View.GONE);
String[] bookingDateSplit = booking.getVisitDate().split("-");
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, Integer.parseInt(bookingDateSplit[0]));
calendar.set(Calendar.MONTH, (Integer.parseInt(bookingDateSplit[1]) - 1));
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(bookingDateSplit[2]));
dayOfWeek.setText(util.getDayNameFromInt(calendar.get(Calendar.DAY_OF_WEEK)));
dayOfMonth.setText(bookingDateSplit[2]);
month.setText(util.getMonthShortNameFromInt(calendar.get(Calendar.MONTH)));
}
else
{
User consumer = booking.getConsumer();
if (consumer.getAvatar() != null && consumer.getAvatar().length() > 0)
{
Glide.with(context).load(consumer.getAvatar()).circleCrop().placeholder(R.drawable.vector_portrait_placeholder).into(avatar);
} else
{
avatar.setBackground(util.getTextDrawable(context, consumer.getFirstName()));
}
name.setText(String.format(Locale.getDefault(), "%d - %s %s", booking.getOrderId(), consumer.getFirstName(), consumer.getLastName()));
status.setText(booking.getStatus());
charges.setText(activeAppointment.getAppointmentType().equalsIgnoreCase(Constants.APPOINTMENT_TYPE_VISIT) ? booking.getVisitCharges() : booking.getBaseRate());
timeSlot.setText(activeAppointment.getSlotTitle());
String[] appointmentDateSplit = activeAppointment.getDate().split("-");
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, Integer.parseInt(appointmentDateSplit[0]));
calendar.set(Calendar.MONTH, (Integer.parseInt(appointmentDateSplit[1]) - 1));
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(appointmentDateSplit[2]));
dayOfWeek.setText(util.getDayNameFromInt(calendar.get(Calendar.DAY_OF_WEEK)));
dayOfMonth.setText(appointmentDateSplit[2]);
month.setText(util.getMonthShortNameFromInt(calendar.get(Calendar.MONTH)));
}
if(booking.getStatus().equalsIgnoreCase(Constants.JOB_STATUS_SCHEDULED)
|| booking.getStatus().equalsIgnoreCase(Constants.JOB_STATUS_IN_PROGRESS)
|| booking.getStatus().equalsIgnoreCase(Constants.JOB_STATUS_COMPLETED)
|| booking.getStatus().equalsIgnoreCase(Constants.JOB_STATUS_CANCELLED))
{
cancel.setVisibility(View.GONE);
}
else
{
cancel.setVisibility(View.VISIBLE);
}
}
@Override
public void onClick(View view)
{
Booking clickedBooking = bookings.get(getAdapterPosition());
if (view.getId() == R.id.cancel)
{
new MaterialDialog.Builder(context)
.title(R.string.cancel_booking)
.content(R.string.are_you_sure_cancel_booking)
.positiveText(R.string.cancel_booking)
.positiveColor(ContextCompat.getColor(context, R.color.quantum_googred500))
.onPositive((dialog, which) -> cancelOrder(util.getToken(context), clickedBooking.getOrderId(), getAdapterPosition()))
.negativeText(R.string.dismiss)
.show();
} else
{
if (bookings.get(getAdapterPosition()).getStatus().equalsIgnoreCase(Constants.JOB_STATUS_UNASSIGNED) || bookings.get(getAdapterPosition()).getStatus().equalsIgnoreCase(Constants.JOB_STATUS_PENDING))
{
Toasty.info(context, R.string.no_booking_details_yet).show();
} else
{
context.startActivity(new Intent(context, BookingDetailsActivity.class)
.putExtra(Constants.ATTACHED_BOOKING, bookings.get(getAdapterPosition())));
}
}
}
}
public class LoadingViewHolder extends RecyclerView.ViewHolder
{
LoadingViewHolder(View itemView)
{
super(itemView);
}
}
public void setMoreDataAvailable(boolean moreDataAvailable)
{
isMoreDataAvailable = moreDataAvailable;
}
/* notifyDataSetChanged is a final method so we can't override it
call adapter.notifyDataChanged() after updating the list */
public void notifyDataChanged()
{
notifyDataSetChanged();
isLoading = false;
}
public interface OnLoadMoreListener
{
void onLoadMore();
}
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener)
{
this.loadMoreListener = loadMoreListener;
}
private void cancelOrder(String authorization, long bookingId, int position)
{
APIClient.getClient().cancelOrder(authorization, FSSApplication.companyToken, new RequestCancelOrder(bookingId)).enqueue(new Callback<ResponseGenericString>()
{
@Override
public void onResponse(Call<ResponseGenericString> call, Response<ResponseGenericString> response)
{
if (response.isSuccessful())
{
if (response.body().getStatus())
{
Toasty.success(context, response.body().getMessage()).show();
bookings.remove(position);
notifyDataChanged();
} else
{
Toasty.error(context, response.body().getMessage()).show();
}
}
}
@Override
public void onFailure(Call<ResponseGenericString> call, Throwable t)
{
Toasty.error(context, t.getMessage()).show();
}
});
}
}