RecyclerView dengan DiffUtil (Android)

DiffUtil untuk apa?

RecyclerView biasa digunakan untuk tampilan list di dalam aplikasi Android. RecyclerView dengan view holdernya telah melakukan optimasi pada tampilan berbentuk list yang sebelumnya menggunakan ListView. Disini saya ingin melakukan optimasi lagi untuk pemakaian RecyclerView, melihat beberapa hari yang lalu saya menemukan bahwa android telah merilis suatu library baru yang bisa melakukan pengecekan perbedaan data pada list. Sehingga saat ingin melakukan update data, RecyclerView hanya akan melakukan melakukan update data pada bagian data yang berbeda. Hal ini bisa kita pakai untuk mengoptimasi recyclerview.

DiffUtil untuk RecyclerView agar lebih optimal dalam melakukan update data.

Berikut tentang DiffUtil

DiffUtil android api level berapa? tidak perlu pusing untuk api level berapanya, karena DiffUtil sudah ada di dalam android support V7 terbaru.

DiffUtil mempunyai callback yang didalamnya terdapat beberapa fungsi yang mengembalikan boolean, dimana callback inilah yang akan dipakai DiffUtil untuk melakukan pengecekan. Berikut beberapa method/fungsi dari callbacknya :

  • getOldListSize()-> Mengembalikan ukuran list yang lama.
  • getNewListSize()-> Mengembalikan ukuran list yang baru.
  • areItemsTheSame(int oldItemPosition, int newItemPosition)-> Dipanggil oleh DiffUtil untuk memutuskan apakah dua object memiliki sama Item. Jika item Anda memiliki id yang unik, fungsi ini harus mengecek apakah id mereka sama.
  • areContentsTheSame(int oldItemPosition, int newItemPosition)-> Mengecek apakah dua item memiliki data yang sama. Dimana biasanya pengecekan ini tergantung pada content UI Anda. Fungsi ini dipanggil oleh DiffUtil hanya jika areItemsTheSame mengembalikan true.
  • getChangePayload (int oldItemPosition, int newItemPosition)-> Jika areItemTheSame mengembalikan true dan areContentsTheSame mengembalikan false DiffUtil memanggil fungsi ini untuk mendapatkan Object tentang perubahan.

Contoh Kasus

Disini saya akan membuat aplikasi daftar dari sebuah mobil, dimana terdapat nama, rate, dan tahun pembuatan. Saya membuatnya menggunakan kotlin.

Implementasi Callback:

 interface DiffUtilAdapterCallback {  
  fun RecyclerView.Adapter<*>.notifyDiffUtil(old: List, new: List,  
    compareItem: (Car, Car) -> Boolean, compareContents: (Car, Car) -> Boolean) {  
   val diff = DiffUtil.calculateDiff(object : Callback() {  
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = 
      compareItem(old[oldItemPosition], new[newItemPosition]) 
 
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = 
      compareContents(old[oldItemPosition], new[newItemPosition]) 
 
    override fun getOldListSize() = old.size 
 
    override fun getNewListSize() = new.size  
   })  
   diff.dispatchUpdatesTo(this)  
  }  
 }  

Code yang tebal adalah implementasi dari callbacknya yang dibungkus lagi ke dalam interface. Dibungkus dengan interface lagi agar bisa diimplementasi di adapter. Di fungsi areItemsTheSame dan AreContentsTheSame saya membuat fungsi yang bisa dipassing, sedangkan 2 fungsi getSize cukup diimplementasi di dalam,  agar kita cukup konsentrasi pada perbandingannya saja.

Adapter dengan Implementasi DiffUtilAdapterCallback:

 class CarAdapter() : RecyclerView.Adapter(), DiffUtilAdapterCallback {  
   var compareItem : (Car, Car) -> Boolean = {a, b -> false}  
   var compareContent : (Car, Car) -> Boolean = {a, b -> false}  
   var items: List by Delegates.observable(emptyList()) {  
     a, b, c->  
     notifyDiffUtil(b, c, compareItem,compareContent)  
   }  
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {  
     val inflater = LayoutInflater.from(parent.context)  
     val view = inflater.inflate(R.layout.car_item, parent, false)  
     return ViewHolder(view)  
   }  
   override fun getItemCount() = items.size  
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {  
     holder.bind(items[position])  
   }  
   class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {  
     fun bind(car: Car) = with(itemView) {  
       car_name.text = car.name  
       car_rate.text = "#${car.rating}"  
       car_year.text = "${car.year} tahun"  
     }  
   }  
 }  

Adapter tinggal mengimplementasi interface yang saya buat. Saya memakai delegate observable, agar setiap items berubah dicompare dengan items yang lama. Dan perbandingan item dan contentnya didapat dari fungsi yang nantinya dipassing dari luar.

Berikut class MainActivitynya:

 class MainActivity : AppCompatActivity() {  
  val provider = CarProvider()  
  val adapter = CarAdapter()  
  val handler = Handler()  
  val runnable = { adapter.cars = provider.getCars(this); getCarsByPeriod() }  
  override fun onCreate(savedInstanceState: Bundle?) {  
   super.onCreate(savedInstanceState)  
   setContentView(R.layout.activity_main)  
   recycler_view.layoutManager = LinearLayoutManager(this)  
   recycler_view.adapter = adapter  
   adapter.compareItem = {a, b -> a.id == b.id}  
   adapter.compareContent = {a, b -> a.name.equals(b.name)}  
   runnable()  
  }  
  private fun getCarsByPeriod() {  
   handler.postDelayed(runnable, 2500)  
  }  
 }  

Diatas adalah class activity utamanya, dimana adapter yang sudah dibikin, tinggal diisi data dan cara perbandingannya. Di contoh ini saya membuat tiap 2,5 detik aplikasi merefresh list yang berbeda.

Berikut class Bantuan-bantuan yang saya bikin untuk mempermudah contoh:

 class CarProvider() {  
   var i =0  
   var actorRepository: CarRepository = CarRepository()  
   fun getCars(c: Context): List = when(i) {  
     0 -> {  
       i++  
       Toast.makeText(c, "Urut berdasarkan nama", Toast.LENGTH_SHORT).show()  
       actorRepository.carListSortedByName  
     }  
     1 -> {  
       i++  
       Toast.makeText(c, "8 car", Toast.LENGTH_SHORT).show()  
       actorRepository.carListOriginalMinus2  
     }  
     2 -> {  
       i++  
       Toast.makeText(c, "10 car", Toast.LENGTH_SHORT).show()  
       actorRepository.carListSortedByName  
     }  
     3 -> {  
       i++  
       Toast.makeText(c, "Urut berdasarkan rating", Toast.LENGTH_SHORT).show()  
       actorRepository.carListSortedByRating  
     }  
     else -> {  
       i=0  
       Toast.makeText(c, "Urut berdasarkan tahun", Toast.LENGTH_SHORT).show()  
       actorRepository.carListSortedByYearOfBirth  
     }  
   }  
 }  

Class ini hanya memanipulasi data agar terlihat cara penggunaan DiffUtil.

 class CarRepository {  
  private val carList: List<Car>  
   get() {  
    val cars = ArrayList<Car>()  
    cars.add(Car(1, "Suzuki", 10, 1937))  
    cars.add(Car(2, "Mercy", 9, 1924))  
    cars.add(Car(3, "Ferrari", 8, 1943))  
    cars.add(Car(4, "Mazda", 7, 1940))  
    cars.add(Car(5, "Hammer", 6, 1957))  
    cars.add(Car(6, "Toyota", 5, 1937))  
    cars.add(Car(7, "Mitsubishi", 4, 1956))  
    cars.add(Car(8, "Yamaha", 3, 1937))  
    cars.add(Car(9, "Audi", 2, 1925))  
    cars.add(Car(10, "Jazz", 1, 1954))  
    return cars  
   }    
  val carListSortedByRating: List<Car>  
   get() {  
    val carParamList = carList  
    Collections.sort(carParamList) { a1, a2 -> a1.rating - a2.rating }  
    return carParamList  
   }  
  val carListSortedByName: List<Car>  
   get() {  
    val carParamList = carList  
    Collections.sort(carParamList) { a1, a2 -> a1.name!!.compareTo(a2.name!!) }  
    return carParamList  
   }  
  val carListSortedByYearOfBirth: List<Car>  
   get() {  
    val carParamList = carList  
    Collections.sort(carParamList) { a1, a2 -> a1.year - a2.year }  
    return carParamList  
   }  
  val carListOriginalMinus2: List<Car>  
   get() {  
    val carParamList = carList  
    Collections.sort(carParamList) { a1, a2 -> a1.name!!.compareTo(a2.name!!) }  
    return carParamList.subList(0, carParamList.size-2)  
   }  
 }  

Diatas adalah Data sample yang digunakan untuk mengisi list dan untuk dimanipulasi.

Berikut adalah rekaman hasil dari aplikasi

test4

 

RecyclerView dengan DiffUtil (Android)

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s