sean.jin
Spark Code Blog
sean.jin
전체 방문자
오늘
어제
  • 분류 전체보기
    • 개발공부
      • Kotlin
      • LeetCode
      • Algorithm
      • React
    • 주식차트
    • 책리뷰
    • 유틸리티

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자기개발
  • 부의 추월차선
  • 네마녀의날
  • 주식투자
  • 변동성
  • 책리뷰
  • 책
  • 책추천
  • 아빠와 딸의 주식투자 레슨
  • 주식입문자
  • 쿼드러플위칭데이
  • 트리플 위칭데이
  • 초보
  • 경제
  • 오
  • 주식책리뷰

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
sean.jin

Spark Code Blog

[Kotlin] DiffUtil을 활용한 RecyclerView 개념 및 사용법- DiffUtil RecyclerView, Glide RecyclerView 에 적용하기 - MVVM 뉴스앱 만들기  4편
개발공부/Kotlin

[Kotlin] DiffUtil을 활용한 RecyclerView 개념 및 사용법- DiffUtil RecyclerView, Glide RecyclerView 에 적용하기 - MVVM 뉴스앱 만들기 4편

2021. 6. 27. 12:27
반응형
[Kotlin] diffUtil을 활용한 RecyclerView 개념 및 사용법 - DiffUtil RecyclerView, Glide RecyclerView에 적용하기 - 뉴스앱 만들기 4편

 

저번 포스트에서는 RoomDatabase까지 셋업을 해주었습니다. 일부 코드는 그전 포스트들에서 이미 만들었었던 코드들이 있으므로, MVVM의 전체적인 공부를 원하시면 전 포스트 들을 보고 오시는 걸 추천드립니다. DiffUtil만 이해하기 위해 들어오셨어도 예시 코드를 보시는데 어려움이 없으실 겁니다. 

 

[Kotlin]Room Database 개념 및 사용법 - entity, DAO, database, Typeconverter 생성 - MVVM 뉴스앱 만들기 3편

[Kotlin]Room Database 개념 및 사용법 - entity, DAO, database, Typeconverter 생성 - MVVM 뉴스앱 만들기 3편 저번 포스트에서는 레트로핏을 사용하여 API통신을 구현해보았는데요 이번 편에서..

underdog11.tistory.com

DiffUtil을 사용하는 이유

diffUtil을 사용하지 않은 리사이클 러뷰에서는 아래와 같이 recyclerview adapter에 list를 가져오고, 리스트에 아이템 값이 변할 경우  Mainactivity에서 리스트 값에 "notifydataset changed"를 해줘야 했습니다. diffUtil을 사용하면 리스트의 아이템 값이 변경될 때 좀 더 효율적으로 관리할 수 있게 됩니다. 

diffUtil 개념

diffUtil은 두 개의 리스트(옛날 버전, 새로운 버전) 가있을 때, 두 리스트에 들어있는 아이템들이 다를 때 새로운 아이템으로 업데이트해줍니다. 그리고 백그라운드에서 계산하기 때문에 main thread를 block하지않고 계산하기때문에 효율적입니다. 

이번 예제에서는 옛날 기사의 리스트와 최신 기사의 리스트를 비교하여 업데이트하는 리사이클 러뷰를 만들어보겠습니다. 

 

XML 준비

 

Glide Dependency 추가하기

기사 이미지는 Glide를 이용하여 넣어주겠습니다. 

dependencies {
	// Glide
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    kapt 'com.github.bumptech.glide:compiler:4.11.0'

}
Recyclerview Item layout 만들어주기

리사이클 러뷰에 들어갈 item들의 layout입니다.

  • 레이아웃 폴더 안에 item_article_preview생성
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp">

    <ImageView
        android:id="@+id/ivArticleImage"
        android:layout_width="160dp"
        android:layout_height="90dp"
        android:scaleType="centerCrop"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvSource"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="SOURCE"
        android:textColor="@android:color/black"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivArticleImage" />

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:ellipsize="end"
        android:maxLines="3"
        android:text="TITLE"
        android:textColor="@android:color/black"
        android:textSize="15sp"
        android:textStyle="bold"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/ivArticleImage"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvDescription"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:ellipsize="end"
        android:maxLines="5"
        android:text="DESCRIPTION"
        android:textColor="@android:color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/ivArticleImage"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

    <TextView
        android:id="@+id/tvPublishedAt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="PUBLISHED AT"
        android:textColor="@android:color/black"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvSource" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

DiffUtil 만들어주기

파일 만들어주기

RecyclerViewAdapter를 만들어줘야 합니다.

 

adapter라는 새로운 패키지 아래 NewsAdapter아래에 코틀린 클래스를 만들어주겠습니다. 

Recyclerview에 diffUtil 적용하기

DiffUtil은 recyclerAdapter에서 설정해주어야 합니다.

 

diffUtil 함수는 우리가 만들었던 데이터 클래스인 Article을 타입으로 한 oldItem과 newItem을 비교합니다. oldItem은 업데이트되기 전 리스트고 newItem은 업데이트가 된 리스트입니다. 

 

아래와 같이 DiffUtil 함수를 만들어줍니다.

class NewsAdapter: RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() {


    //viewholder를 만들어줍니다.
    inner class ArticleViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)

    private val differCallback = object: DiffUtil.ItemCallback<Article>(){
        // 이함수는 oldItem과 new Item이 같은지 확인하는 함수입니다.
        override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
            // oldItem과 newItem은 전에 만들었던 article dataclass의 타입을 가지고있습니다.
           
           	// 같은아이템인지 확인하려면 두 아이템에 고유ID를 확인해줘야합니다.
            	
            // 기사마자 다른 url을 가지고있기때문에 data class에서 각 아이템의 url을 확인해주겠습니다.

            return oldItem.url == newItem.url
        }

        override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
            //둘의 컨텐츠도 비교해줍니다.
            return oldItem == newItem
        }

    }
}

diffUtil함수를 implement 해주면 2개의 function이 나옵니다. 

1. areItemTheSame: 같은 콘텐츠인지 각 아이템들의 고유 ID를 확인하여 체크합니다. 이번 예제에서는 dataclass에 id대신 url를 써주도록 하겠습니다. 왜냐하면 아래 코멘트와 같이 id가 없는 기사들도 있기 때문에 url을 대신 고유 ID로 설정하겠습니다. 

2. areContentsTheSame: 콘텐츠가 같은지 확인합니다. 

 

AsyncListdiffer와 RecyclerviewAdapter 함수들 Implement

AsyncListdiffer은 oldList와 newList두개를 가져와서 백그라운드에서 둘이 다른지 계산하는 함수입니다.

 

  • 완성된 newsAdapter 코드
class NewsAdapter: RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() {

    inner class ArticleViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)

    private val differCallback = object: DiffUtil.ItemCallback<Article>(){
        override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {

            return oldItem.url == newItem.url
        }

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

    }

    //추가시작
    val differ = AsyncListDiffer(this,differCallback)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
        return ArticleViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_article_preview,//위에서 만들었던 item layout을 연결
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
        val article = differ.currentList[position]
        
        //각 리스트의 구성요소들을 UI에 연결
        holder.itemView.apply{
            Glide.with(this).load(article.urlToImage).into(ivArticleImage)
            tvSource.text = article.source.name
            tvTitle.text = article.title
            tvDescription.text = article.description
            tvPublishedAt.text = article.publishedAt
            
            각 기사들을 클릭 가능하게 만들어줍니다.
            setOnClickListener{
                onItemClickListener?.let{
                    it(article)
                }
            }
        }
    }

    private var onItemClickListener: ((Article) -> Unit?)? = null

    fun setOnItemClickListener(listener: (Article)->Unit){
        onItemClickListener = listener
    }

    override fun getItemCount(): Int {
        //리사이클러뷰에서는 보통 리스트를 받아서 그 리스트 사이즈를 count하지만 리스트를 asynclistdiffer가 관리하기때문에, differ에 item갯수로 연결해주어야합니다.
        return differ.currentList.size
    }
    //추가끝
}

 

diffUtil LiveData의 Observe로 실행시키기

새로운 데이터가 추가되면 Diffutil이 실행되어야합니다. 그러기위해서 MainActivity에 만들어준 어뎁터에 diffUtil을 가져와서 Observe(Live데이터 함수입니다)안에 넣어주어서 라이브 데이터가 바뀔때마다 diffUtil함수가 실행되게합니다. 

 

[ViewModel class이름].[Viewmodel에 데이터를 불러오는 함수].observe(viewLifecycleOwner, Observer {
         
         	//변경된 값을 Adapter로 submitList를 통해 전달합니다.
           recyclerView.adapter as [어뎁터 class이름]).submitList(it)
           
        })

 

 

 

참고자료

 

DiffUtil  |  Android 개발자  |  Android Developers

DiffUtil public class DiffUtil extends Object java.lang.Object    ↳ androidx.recyclerview.widget.DiffUtil DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list

developer.android.com

 

반응형

'개발공부 > Kotlin' 카테고리의 다른 글

[Kotlin] MVVM Room Database : Navigation Graph 사용법, NavHostFragment, Insert 구성하기 간단한 저장소 만들기 - 2편  (0) 2021.07.26
[Kotlin] MVVM Room Database : Entity, RoomDatabase, DAO, repository, ViewModel, coroutine, MVVM 구성하기 - 간단한 저장소 만들기 1편  (0) 2021.07.09
[Kotlin]Room Database 개념 및 사용법 - entity, DAO, database, Typeconverter 생성 - MVVM 뉴스앱 만들기 3편  (0) 2021.06.26
[Kotlin] 레트로핏 사용법 - Interface, retrofitInstance, constant만들기, GET의미, JSON To Kotlin 플러그인 - 뉴스앱 만들기 2편  (0) 2021.06.22
[Kotlin] 하단 내비게이션 개념 및 사용법 - BottomNavigationView, NavGraph, Fragment, FrameLayout - 뉴스앱 만들기 1편  (0) 2021.06.19
    '개발공부/Kotlin' 카테고리의 다른 글
    • [Kotlin] MVVM Room Database : Navigation Graph 사용법, NavHostFragment, Insert 구성하기 간단한 저장소 만들기 - 2편
    • [Kotlin] MVVM Room Database : Entity, RoomDatabase, DAO, repository, ViewModel, coroutine, MVVM 구성하기 - 간단한 저장소 만들기 1편
    • [Kotlin]Room Database 개념 및 사용법 - entity, DAO, database, Typeconverter 생성 - MVVM 뉴스앱 만들기 3편
    • [Kotlin] 레트로핏 사용법 - Interface, retrofitInstance, constant만들기, GET의미, JSON To Kotlin 플러그인 - 뉴스앱 만들기 2편
    sean.jin
    sean.jin
    앱개발, 알고리즘, JS, Kotlin, 미국 취업준비

    티스토리툴바