춤추는 개발자

[AOS] Room + ViewModel + LiveData + RecyclerView (5) 본문

Android/study_til

[AOS] Room + ViewModel + LiveData + RecyclerView (5)

Heon_9u 2021. 6. 28. 19:38
728x90
반응형

 이번 포스팅에서는 NoteAdapter에서 ListAdapter와 DiffUtil를 활용한 애니메이션을 추가합니다.

 

 notifyDataSetChanged();를 사용하면 전체 데이터 셋을 갱신합니다. 만약, List에서 데이터를 삭제할 경우, 딱딱한(?) UI를 보게 됩니다. 해당 부분을 좀 더 부드럽게 만드는 작업을 진행하도록 하겠습니다.

 

 

1. NoteAdapter

 먼저, 전체 코드는 아래와 같습니다. NoteAdapter가 상속받는 클래스가 변경되었고, 이에 따라 오버라이딩된 메서드와 생성자가 추가되었습니다. 반면에, NoteList와 2개의 메서드가 삭제되었습니다.

 추가된 코드들을 파트별로 확인하도록 하겠습니다.

 

package com.heon9u.aacproject;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class NoteAdapter extends ListAdapter<Note, NoteAdapter.NoteHolder> {
    private OnItemClickListener listener;

    public NoteAdapter() {
        super(DIFF_CALLBACK);
    }

    private static final DiffUtil.ItemCallback<Note> DIFF_CALLBACK = new DiffUtil.ItemCallback<Note>() {
        @Override
        public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
            return oldItem.getId() == newItem.getId();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
            return oldItem.getTitle().equals(newItem.getTitle()) &&
                    oldItem.getDescription().equals(newItem.getDescription()) &&
                    oldItem.getPriority() == newItem.getPriority();
        }
    };

    @NonNull
    @Override
    public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.note_item, parent, false);

        return new NoteHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull NoteHolder holder, int position) {
        Note currentNote = getItem(position);
        holder.textViewTitle.setText(currentNote.getTitle());
        holder.textViewDescription.setText(currentNote.getDescription());
        holder.textViewPriority.setText(String.valueOf(currentNote.getPriority()));
        
    }

    public Note getNoteAt(int position) {
        return getItem(position);
    }

    class NoteHolder extends RecyclerView.ViewHolder {
        private TextView textViewTitle;
        private TextView textViewDescription;
        private TextView textViewPriority;

        public NoteHolder(@NonNull View itemView) {
            super(itemView);
            textViewTitle = itemView.findViewById(R.id.text_view_title);
            textViewDescription = itemView.findViewById(R.id.text_view_description);
            textViewPriority = itemView.findViewById(R.id.text_view_priority);

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

    public interface OnItemClickListener {
        void onItemClick(Note note);
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }
}

 

 

 

먼저, 상속받은 클래스인 ListAdapterDiffUtil에 대해 살펴보겠습니다.

 

[ListAdapter란?]

 ListAdapter란 RecyclerView.Adapter를 베이스로 한 클래스로 RecyclerView의 List 데이터를 표현해주며 List를 백그라운드 스레드에서 diff(차이)를 처리하는 특징이 있습니다.

 

 해당 클래스는 AsyncListDiffer 아이템 접근 및 카운팅에 대한 Adapter 공통 기본 동작을 구현하는 편리한 Wrapper 입니다. LiveData <List>를 사용하면 어댑터에 데이터를 쉽게 제공할 수 있지만, 필수는 아닙니다.

 

 

 ListAdapter과 관련된 메서드는 아래와 같습니다.

 

getItem(position: Int) : protected method로 클래스 내부에서 사용하며 Adapter 내의 List를 인덱싱할때 사용합니다. 기존에는 Adapter내에서 List<Note> notes로 선언하여 notes.get(position) 식으로 사용습니다.

 반면에, ListAdapter + Diffutil에서는 아이템 List도 알아서 관리해주기 때문에 기본적으로 리스트 선언이 필요없고 이 리스트의 아이템을 가져오는데 사용(대체)됩니다.

 

getCurrentList() : Adapter가 가지고 있는 리스트를 가져올 때 사용합니다.

 

submitList(List list): 리스트 항목을 변경하고 싶을 때 사용합니다. 기존 Adapter의 add(), notifyDataSetChanged()를 대체합니다.

 

 

 

 [DiffUtil이란?]

 안드로이드 문서에서는 위와 같이 설명합니다. 이를 간단하게 요약하자면, Adapter에서 현재 List와 교체될 데이터 List를 비교하여 변경사항을 알아내는 클래스로 RecyclerView에서 기존 List의 변화가 있을 시, 전체를 갈아치우는게 아니라 변경된 데이터만 빠르게 바꿔주는 역할을 합니다.

 즉, 바뀐 부분만 골라서 List를 갱신하는 역할을 하는 것입니다.

 

 

 

<DiffUtil.ItemCallback<Note>>

private static final DiffUtil.ItemCallback<Note> DIFF_CALLBACK = new DiffUtil.ItemCallback<Note>() {
    @Override
    public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
        return oldItem.getId() == newItem.getId();
    }

    @Override
    public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
        return oldItem.getTitle().equals(newItem.getTitle()) &&
                oldItem.getDescription().equals(newItem.getDescription()) &&
                oldItem.getPriority() == newItem.getPriority();
    }
};

 

 해당 코드는 List의 객체인 Item이 같은지 확인하는 단계입니다. Note의 PK인 id를 비교함과 동시에 수정이 가능한 Contents들을 확인합니다.

 

 

 

2. MainActivity

 위에 설명한 ListAdapter의 submitList() 메서드를 활용합니다.

 

noteViewModel.getAllNotes().observe(this, new Observer<List<Note>>() {
      @Override
      public void onChanged(List<Note> notes) {
          adapter.submitList(notes);
      }
  });

 

 

 

여기까지 Room + ViewModel + LiveData + RecyclerView을 활용한 예제를 마치도록 하겠습니다. 해당 예제에 적용할 수 있는 DataBinding과 Androidx 버전의 Activity Result API 사용은 추후, 진행하겠습니다.

 

 저도 아직 배우는 입장이기 때문에 관련 기술들을 습득하여 해당 예제에 적용 후, 개인 프로젝트에 적용할 예정입니다. 하루 빨리 개인 프로젝트의 코드 리팩토링을 마친 후, 스프링 부트 공부를 해야겠습니다..ㅠ

 

728x90
반응형