Przygotowując się na jedno ze szkoleń z programowania na Androida, natknąłem się na pewną sytuację podczas pisania adaptera dla RecyclerView. Projekt aplikacji mobilnej jest z założenia bardzo prosty: najprostsza aplikacja listy zadań do zrobienia (lista TODO) z możliwością oznaczania zadań jako wykonane oraz ich usuwania. Jednak podczas tak prostej czynności, jaką jest usuwanie elementu z listy, może pojawić się problem z pobraniem niewłaściwej jego pozycji. Czy wiesz, w jaki sposób pobrać właściwą pozycję dla elementu listy?
Każdy adapter dla RecyclerView musi implementować metodę onBindViewHolder()
, która może wyglądać w następujący sposób:
@Override
public void onBindViewHolder(@NonNull ListItemHolder holder, int position) {
}
Jak widać, jako drugi parametr jest zwracana pozycja elementu listy, który w danej chwili jest zawiązywany. Z tego punktu widzenia sprawa wydaje się prosta więc co jeśli wykorzystamy ten parametr do usuwania elementu z listy jak w ten sposób:
@Override
public void onBindViewHolder(@NonNull final ListItemHolder holder, final int position) {
holder.bDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listItems.remove(position);
notifyItemRemoved(position);
}
});
}
W takim przypadku wewnątrz metody onClick()
pozycja elementu niestety nie będzie już właściwa i w ten sposób możemy usunąć zupełnie inny element niż ten, dla którego przycisk został kliknięty. Dzieje się tak, ponieważ RecyclerView nie aktualizuje pozycji dla już zawiązanych pozycji, ale tylko dla nowych i może być tak, że po kliknięciu przycisku usuwania piątego elementu, zostanie usunięty element, który znajduje się na pozycji zerowej.
Jak więc właściwie poradzić sobie z tym problemem? Otóż klasa RecyclerView.ViewHolder
dostarcza nam metodę getBindingAdapterPosition()
.
Przykład użycia getBindingAdapterPosition()
Sposób działania tej metody jest następujący: po jej wywołaniu następuje odpytanie do adaptera o najbardziej zaktualizowaną pozycję dla uchwytu (holdera), dzięki czemu dostajemy zawsze aktualną pozycję względem adaptera:
@Override
public void onBindViewHolder(@NonNull final ListItemHolder holder, int position) {
holder.bDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int itemPosition = holder.getBindingAdapterPosition();
listItems.remove(itemPosition);
notifyItemRemoved(itemPosition);
}
});
}
Należy przy tym zwrócić uwagę, że powyższe zastosowanie będzie się odnosiło do powiadomienia adaptera o zmianach poprzez użycie metod notifyItem*()
, ponieważ notifyDataSetChanged()
sprawi, że cała lista ulegnie przewiązaniu i wówczas getBindingAdapterPosition()
może zwrócić pozycję o wartości -1
, czyli NO_POSITION
(również w przypadku, gdy element nie znajduje się już na liście). Jeśli pojawi się u nas taka sytuacja, wówczas najlepiej jest zignorować kliknięcie, ponieważ nie wiemy, co powinniśmy z nim zrobić.