왜 HealthSetLogAdapter의 item을 static 으로 선언하셨나요? 아이템이 자식 뷰홀더간에 공유되면 님이 보시는 증상이 생길 수 있겠네요.
그리고 제가 테스트를 해보니, 다시 생각해보셔야할 코드 디자인의 문제점이 있습니다.
holder.getButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 추가하기 버튼이 클릭되면 아이템 추가
setList list = new setList(String.valueOf(set++), "0kg", "0 reps");
HealthLogSetAdapter.addItem(list);
notifyDataSetChanged();
}
});
부모어댑터와 자식어댑터가 공유하는 데이터타입이 일치하지 않는 다는 것입니다. 님의 경우는 부모어댑터에는
ArrayList<String> registeredLogs 자식 어댑터 에는 String + List<String> 을 사용하고 있습니다. 그리고 자식어댑터의 아이템에 대한 컨트롤을 부모어댑터 안에서 하려고 하고 있습니다. 그럼, 다음 질문에 답을 해보세요.
님이 사용하는 데이터가 예를 들어 로컬데이터베이스 또는 서버에서 오는 거라고 생각해 보죠. 그리고 님이 자식어댑터에 뭔가를 추가하면 서버에 저장이 되고 이것이 화면에 반영이 되어야 한다면 위의 구조에서는 어떻게 처리를 하실 수 있나요?
우선, 부모어댑터에서 사용해야 하는 데이터 타입은 자식어댑터의 데이터타입을 포함해야 합니다.
public class Routine {
private final String equipmentName;
private List<SetsItem> setsItems;
...
public static Routine newEmptyRoutine(String equipmentName) {
return new Routine(equipmentName, new ArrayList<>());
}
}
public class SetsItem {
private final Sets sets;
private final Weight weight;
private final Reps reps;
...
public static SetsItem newEmptySetsItem() {
return new SetsItem(new Sets(1), new Weight(0), new Reps(0));
}
}
public enum SetsUnit {
NONE(""),
KG("kg"),
REPS("reps");
private final String text;
SetsUnit(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
public abstract class SetsField {
protected final int value;
protected SetsUnit unit;
...
public String getDisplayText() {
return value + " " + unit.getText();
}
}
public class Reps extends SetsField {
public Reps(int value) {
super(value, SetsUnit.REPS);
}
}
public class Sets extends SetsField {
public Sets(int value) {
super(value, SetsUnit.NONE);
}
}
public class Weight extends SetsField {
public Weight(int value) {
super(value, SetsUnit.KG);
}
}
위와 비슷한 구조가 되어야 어댑터 안에서 다음과 같이 처리가 가능합니다.
public class RoutineAdapter extends RecyclerView.Adapter<RoutineViewHolder> {
private List<Routine> routines = new ArrayList<>();
private RoutineViewHolder.Listener routineListener;
public RoutineAdapter(List<Routine> routines) {
this.routines = routines;
}
public void setItems(List<Routine> routines) {
this.routines = routines;
notifyDataSetChanged();
}
public void updateRoutine(Routine routine) {
int index = this.routines.indexOf(routine);
if (index < 0) throw new NoSuchElementException("Cannot find routine " + routine);
this.routines.set(index, routine);
notifyItemChanged(index);
}
public void setRoutineListener(RoutineViewHolder.Listener routineListener) {
this.routineListener = routineListener;
}
@NonNull
@Override
public RoutineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_routine, parent, false);
return new RoutineViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RoutineViewHolder holder, int position) {
Routine routine = routines.get(position);
holder.bind(routine, routineListener);
}
@Override
public int getItemCount() {
return routines.size();
}
}
public class RoutineViewHolder extends RecyclerView.ViewHolder {
public interface Listener {
void onAddItem(Routine routine);
}
private final TextView equipmentTxt;
private final Button addBtn;
private final RecyclerView setsRcv;
private final SetsAdapter adapter;
public RoutineViewHolder(@NonNull View itemView) {
super(itemView);
equipmentTxt = itemView.findViewById(R.id.equipmentTxt);
addBtn = itemView.findViewById(R.id.addBtn);
setsRcv = itemView.findViewById(R.id.setsRcv);
adapter = new SetsAdapter(Collections.emptyList());
setsRcv.setAdapter(adapter);
}
public void bind(Routine routine, Listener listener) {
equipmentTxt.setText(routine.getEquipmentName());
adapter.setItems(routine.getSetsItems());
addBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onAddItem(routine);
}
});
}
}
자식어댑터에서의 처리도 간결해 집니다.
public class SetsAdapter extends RecyclerView.Adapter<SetsViewHolder> {
private List<SetsItem> items = new ArrayList<>();
public SetsAdapter(List<SetsItem> items) {
this.items = items;
}
public void setItems(List<SetsItem> items) {
this.items = items;
notifyDataSetChanged();
}
@NonNull
@Override
public SetsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sets, parent, false);
return new SetsViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SetsViewHolder holder, int position) {
holder.bind(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
}
public class SetsViewHolder extends RecyclerView.ViewHolder {
TextView setsTxt, weightTxt, repsTxt;
public SetsViewHolder(@NonNull View itemView) {
super(itemView);
setsTxt = itemView.findViewById(R.id.setsTxt);
weightTxt = itemView.findViewById(R.id.weightTxt);
repsTxt = itemView.findViewById(R.id.repsTxt);
}
public void bind(SetsItem item) {
setsTxt.setText(item.getSets().getDisplayText());
weightTxt.setText(item.getWeight().getDisplayText());
repsTxt.setText(item.getReps().getDisplayText());
}
그리고 바뀐 데이터 구조를 이용하면 외부에서 아이템을 추가하거나 삭제하는 부분도 컨트롤이 더 수월해집니다.
public class MainActivity extends AppCompatActivity {
private RoutineAdapter routineAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();
}
private final List<Routine> routines = new ArrayList(Arrays.asList(
Routine.newEmptyRoutine("Decline bench press"),
Routine.newEmptyRoutine("Incline bench press")
));
private void bindViews() {
RecyclerView routineRcv = findViewById(R.id.routineRcv);
routineRcv.addItemDecoration(new DividerItemDecoration(routineRcv.getContext(), RecyclerView.VERTICAL));
routineAdapter = new RoutineAdapter(routines);
routineAdapter.setRoutineListener(new RoutineViewHolder.Listener() {
@Override
public void onAddItem(Routine routine) {
addSets(routine);
}
});
routineRcv.setAdapter(routineAdapter);
}
private void addSets(Routine routine) {
List<SetsItem> setsItems = routine.getSetsItems();
setsItems.add(SetsItem.newEmptySetsItem());
routineAdapter.updateRoutine(routine);
}
}
데이터 핸들링 관련된 로직, 리스너 관련된 로직은 거의 대부분의 경우에 액티비티나 프레그먼트에서 컨트롤 하는게 일반적입니다. 왜냐하면 데이터의 흐름은 어댑터에서 관여해서는 안되는 사항이기 때문입니다. 어댑터는 주어진 데이터를 받아서 뷰홀더에게 그려달라고 요청하는 역할만 하면 됩니다. 그리고 데이터를 가져오고 아이템을 추가, 저장하는 일은 별도의 클래스가 백그라운드 스레드에서 해주는게 좋습니다.