[Android/Kotlin] RecyclerView에서 ListAdapter +DiffUtil 사용해보기

핸디·2021년 6월 29일
0

안드로이드

목록 보기
4/9

Adapter의 역할

데이터를 받아오고 이를 레이아웃에 직접 연결하는 함수를 실행시키는 클래스
= ListView나 App의 특정 데이터와 RecyclerView의 view를 Bind하는 것을 도와준다

RecyclerViewAdapter 같은 경우, 전체 리스트를 통째로 업데이트해야 할때 100개의 아이템들이 있다면 100개가 모두 업데이트된다.
그런데 안바뀌는 항목이 더 많다면..? 비효율적이다
-> 그래서 DiffUtil 이 있다

DiffUtil

기존 리스트와 업데이트 된 리스트의 차이를 계산하고 실제로 변환할 리스트 아이템들의 결과를 반환하는 유틸리티 클래스
주로 RecyclerView Adpater의 업데이트를 계산하는데 사용되고 ListAdapter에서 DiffUtil을 활용해서 차이점을 계산한다.

DiffUtil 사용법

DiffUtil.Callback 이라는 기능을 구현해야 한다!

 //diffutil -> 새로운 아이템 할당할지 말지 판단해주는 ->콜백 구현해야함
companion object{
        val diffUtil=object:DiffUtil.ItemCallback<Book>(){
        
            // 두 아이템이 동일한 아이템인지 체크. 보통 고유한 id를 기준으로 비교
            override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
               return oldItem.id==newItem.id
            }
            
	// 두 아이템이 동일한 내용을 가지고 있는지 체크. areItemsTheSame()이 true일때 호출됨
            override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
                return oldItem==newItem
            }


        }

ListAdapter?

간단히 말해서, DiffUtil을 활용해서 리스트를 업데이트 할 수 있는 기능을 추가한 Adapter

AsyncListDiffer 클래스를 쉽게 사용하기 위해 ListAdapter를 활용할 수 있다. ListAdapter의 생성자에서 DiffUtil.ItemCallback을 인자로 넘겨주면서 사용한다.List 데이터를 표현해주며 List를 백그라운드 스레드에서 diff(차이)를 처리하는 특징이 있다.

  • ListAdapter<데이터클래스, 리사이클러뷰 뷰홀더>를 인자로 받는다.
    어댑터 내에서 리스트를 정의하는게 아니라 리스트 자체에서 데이터 리스트를 정의하기 때문
  • 그래서 일반적인 RecyclerViewAdapter와 다르게 getItemCount() 구현안해도 됨

ListAdapter에서 사용할 수 있는 메서드

  1. getCurrentList() : 현재 리스트를 반환한다.
  2. onCurrentListChanged() : 리스트가 업데이트 되었을 때 실행할 콜백을 지정할 수 있다.
  3. submitList(List) : 리스트 데이터를 교체할 때 사용한다.

소스코드

class BookAdapter:ListAdapter<Book,BookAdapter.BookItemViewHolder>(diffUtil) {

//ListAdapter<데이터클래스,리사이클러뷰 뷰홀더> 를 인자로 받는다-
  

  inner class BookItemViewHolder(private val binding: ItemBookBinding):RecyclerView.ViewHolder(binding.root){
//뷰홀더: 내가 넣고자하는 data를 실제 레이아웃의 데이터로 연결시키는 기능
      fun bind(bookModel:Book){//view와 데이터를 연결시키는 함수-/>뷰에 데이터 넣음
          binding.titleTextview.text=bookModel.title
          binding.descriptionTextView.text=bookModel.description
          Glide
              .with(binding.coverImageView.context) //context가 어댑터에 없다 -> 뷰에 있겠죠?
              .load(bookModel.coverSmallUrl)
              .into(binding.coverImageView)

      }
  }

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
      //미리 만들어진 뷰홀더가 없는 경우 새로 생성하는 함수(레이아웃 생성)
      return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context),parent,false))
  }

  override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
      //실제로 뷰홀더가 뷰에 그려졌을때 데이터를 뿌려주는 바인드해주는 함수(뷰홀더가 재활용될때 실행)
      holder.bind(currentList[position])
  }

//diffutil사용하려면 diffutil.callback이라는 기능을 구현해야함
  companion object{
      val diffUtil=object:DiffUtil.ItemCallback<Book>(){
          override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
             return oldItem.id==newItem.id
          }

          override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
              return oldItem==newItem
          }


      }
  }
}

#MainActivity.kt에 추가

class MainActivity : AppCompatActivity() {

  private lateinit var binding:ActivityMainBinding
  private lateinit var adapter:BookAdapter

  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      //setContentView(R.layout.activity_main)
      binding=ActivityMainBinding.inflate(layoutInflater)
      setContentView(binding.root)

      adapter= BookAdapter()
      initBookRecyclerView()



      val retrofit= Retrofit.Builder()
              .baseUrl("https://book.interpark.com")
              .addConverterFactory(GsonConverterFactory.create()) // Json데이터를 사용자가 정의한 Java 객채로 변환해주는 라이브러리
              .build() //레트로핏 구현체 완성!

      val bookService=retrofit.create(BookService::class.java)

      bookService.getBestSeller("38845BE9BD0EBEDF271A2D5BC770C5BEEBB2D38910F504545CE384C6692DA6D4")
              .enqueue(object: Callback<BestSellerDto>{
                  override fun onFailure(call: Call<BestSellerDto>, t: Throwable) {
                      //todo 실패처리
                      Log.d(TAG,t.toString())
                  }

                  override fun onResponse(call: Call<BestSellerDto>, response: Response<BestSellerDto>) {
                      //todo 성공처리

                      if(response.isSuccessful.not()){
                          return
                      }
                      response.body()?.let{
                          //body가 있다면 그안에는 bestSellerDto가 들어있을것
                          Log.d(TAG,it.toString())

                          it.books.forEach{ book->
                              Log.d(TAG,book.toString())
                          }
                          adapter.submitList(it.books)
                          //ListAdapter는 내부적으로 AsyncListDiffer를 사용하면서, RecyclerView의 adapter처럼 이용이 가능합니다.
                          // 따라서 우리는 최종적으로 ListAdapter를 상속하는 adapter 클래스를 만들고, ListAdapter의 파라미터에 diffutil의 callback을 구현해서 넘겨주면
                          // 내부에서 submitlist( 바뀔 데이터 ) 라는 하나의 메서드로 모든 작업을 처리 할 수 있습니다!!!!
                      }


                  }

              })

  }

참고

https://velog.io/@l2hyunwoo/Android-RecyclerView-DiffUtil-ListAdapter
-> 사용 이유 / 설명과 예제

https://youngest-programming.tistory.com/474
-> RecyclerView->ListAdapter+DiffUtil 예제정리

https://week-year.tistory.com/108
-> 설명

0개의 댓글