CategoriesAndroid

Optimalkan RecyclerView dengan ListAdapter

Apakah kamu masih menggunakan notifyDatasetChanged() untuk merefresh item di RecyclerView Adapter ? Cara ini sudah tidak direkomendasikan lagi, jika kamu mengetik kode ini di Android Studio akan muncul warning yang memberitahukan jika cara ini sudah tidak effisien jika dipakai untuk merefresh list. Cara yang direkomendasikan saat ini adalah menggunakan DiffUtilCallback. Kamu perlu menambahkan beberapa kode di class Adapter kamu.

Kenapa notifyDataSetChanged sudah tidak efisien lagi ?

Memakai notifyDataSetChanged() artinya kamu memaksa RecyclerView untuk me-refresh semua item, tak peduli hanya satu item yang berubah atau ada ratusan item di listmu. Ini bikin performa jadi berat, animasi list hilang, dan baterai pengguna cepat habis. Kalau di list cuma ada beberapa item, mungkin efeknya tak terasa. Tapi jika item makin banyak, aplikasi bisa terasa berat bahkan kadang terasa “flicker” saat scrolling.

Penerapan DiffUtilCallback di RecyclerView.Adapter

1. buat class DiffUtilCallback.

class UserDiffCallback(
    private val oldList: List<User>,
    private val newList: List<User>
) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // Cek apakah item yang dibandingkan sama (biasanya lewat id unik)
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // Cek apakah isi datanya juga sama (misal property lain yang bisa berubah)
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

2. Pada method updateData, tambahkan beberapa kode untuk menerapkan DiffUtilCallback

class UserAdapter(private var userList: List<User>) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
    // ... kode ViewHolder

    fun updateData(newList: List<User>) {
        val diffResult = DiffUtil.calculateDiff(UserDiffCallback(userList, newList))
        userList = newList
        diffResult.dispatchUpdatesTo(this)
    }

    // ... fungsi onCreateViewHolder, onBindViewHolder, getItemCount, dll
}

Selesai, RecyclerView.Adapter kamu sudah menerapkan DiffUtilCallback.

Mungkin kamu merasa kode yang dibutuhkan cukup banyak sehingga kamu tetap menggunakan notifyDataSetChanged() karena kode nya cuma 1 baris. Sebenarnya ada Adapter yang sudah build in dengan DiffUtilCallback yang bisa digunakan dengan RecyclerView yang lebih efisien dan smooth daripada Adapter RecyclerView biasa. Adapter ini adalah ListAdapter

Apa itu ListAdapter

ListAdapter adalah turunan dari RecyclerView.Adapter yang sudah dibekali dengan mekanisme pembanding data otomatis menggunakan DiffUtil. Dengan ListAdapter, kamu tinggal “submit” list baru, selebihnya ListAdapter yang akan menghitung perubahan (item mana yang bertambah, berkurang, atau sekadar berubah isi) dan mengupdate tampilan sesuai kebutuhan

ListAdapter sudah otomatis menjalankan diffing di background thread, sehingga proses cek perubahan data tidak akan mengganggu main thread. Kamu tinggal fokus ke data dan tampilan, tanpa perlu bikin perbandingan manual. Cukup panggil method bawaan dari ListAdapter yaitu .submitList(newList), ListAdapter akan menangani semua perhitungannya untukmu—termaksud men-trigger animasi insert, update, maupun delete pada item.

Penerapan ListAdapter

1. Buat class DiffUtilCallback. tapi kali extends ke DiffUtil.ItemCallback<Model>()

private class UserDiffCallback : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(
            oldItem: User,
            newItem: User
        ): Boolean {
            return oldItem.id == newItem.id
        }

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

    }

    Sekilas kode DiffUtil.ItemCallback<Model>() lebih pendek daripada extends ke DiffUtil.Callback() biasa

    2. Buat class Adapter yang extends ke ListAdapter<Model, ViewHolderClass>(DiffCallbackClass())

    class UserAdapter(private val onItemClick:(Int) -> Unit) : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {
        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): ViewHolder {
            val binding = ItemUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return ViewHolder(binding)
        }
    
        override fun onBindViewHolder(
            holder: ViewHolder,
            position: Int
        ) {
            holder.bind(getItem(position))
        }
    
        inner class ViewHolder(private val binding : ItemUserBinding) : RecyclerView.ViewHolder(binding.root) {
            fun bind(item : User) {
                binding.apply {
                    txtName.text = item.name
                    txtAddress.text = item.address
    
                    root.setOnClickListener {
                        if (!item.isSelected) {
                            val position = bindingAdapterPosition
                            if (position != RecyclerView.NO_POSITION) {
                                onItemClick(position)
                            }
                        }
                    }
                }
            }
        }
    
        fun changeName(position: Int, newName:String) {
            // Update List
            val updatedList = currentList.mapIndexed { index, userModel ->
                if (index == position) {
                    userModel.copy(name = newName)
                }
            }
            // Submit Updated List
            submitList(updatedList)
        }
    }
    • Pada method onBindViewHolder, untuk mendapatkan item kamu bisa gunakan method bawaan ListAdapter getItem(position)
    • Untuk mengupdate item pada List di posisi tertentu. kamu bisa gunakan method submitList(newList), untuk mendapatkan list yang ada di Adapter kamu bisa memanggil variable bawaan currentList

    Sekilas kode ListAdapter lebih pendek daripada RecyclerView.Adapter biasa. karena kamu tidak perlu menginisiasi List yang dipakai, tidak perlu menentukan itemCount, dan tidak perlu mengimplementasi DiffUtilCallback secara manual di method untuk Update Data. Semua sudah otomatis di handle oleh ListAdapter

    3. Untuk mengupdate List dari Activity / Fragment. kamu bisa gunakan method submitList(newList)

    class ExampleActivity : AppCompatActivity() {
        private lateinit var userAdapter : UserAdapter
    
        override fun onCreate(savedInstanceState: Bundle?) {
            // Kode di OnCreate
            userAdapter = UserAdapter { position ->
               // Kode onItemClick
            }
            viewModel.userList.observe(this) { newList ->
               userAdapter.submitList(newList)
            }
        }
    }

    Selamat kamu sudah menerapkan ListAdapter untuk RecyclerView kamu

    Kapan harusnya menggunakan notifyDataSetChanged

    notifyDataSetChanged() hanya disarankan jika memang semua data berubah total dan kamu butuh refresh cepat, biasanya dalam kasus yang sangat spesifik (misal reset data seluruhnya dalam skenario yang sangat jarang). Tapi untuk list harian, update sebagian, atau perubahan bertahap, ListAdapter jauh lebih efisien.

    Kesimpulan

    Jadi keunggulan dari ListAdapter antara lain :

    • Otomatis Menghitung Perubahan Data
      ListAdapter secara otomatis membandingkan data lama dan baru (melalui DiffUtil), sehingga hanya item yang benar-benar berubah yang akan di-update, delete, atau insert pada RecyclerView.
    • Performa Lebih Baik
      Karena ListAdapter tidak perlu merefresh seluruh list dengan notifyDataSetChanged(), performa scrolling dan animasi jadi jauh lebih smooth, apalagi untuk list data yang panjang.
    • Animasi Transisi Lebih Natural
      Dengan update yang lebih spesifik pada item tertentu saja, perubahan pada list (seperti penambahan, penghapusan, atau update data) dapat menampilkan animasi transisi yang lebih halus dan modern pada RecyclerView.
    • Penulisan Kode Lebih Ringkas
      ListAdapter sudah mengintegrasikan DiffUtil langsung sebagai bagian dari class-nya, sehingga tidak perlu lagi membuat dan mengelola class DiffUtil.Callback secara manual dalam adapter.
    • Efisien pada Thread Background
      Proses kalkulasi perbedaan data di DiffUtil berjalan di background thread, sehingga tidak membebani main thread dan aplikasi tetap responsif.
    • Mudah Digunakan untuk List Dinamis
      Menggunakan ListAdapter cukup dengan memanggil submitList(newList) setiap ingin update data, tanpa harus khawatir logic update yang rumit atau bug akibat salah hitung posisi item.
    • Cocok untuk List dengan Frekuensi Update Tinggi
      Untuk fitur yang sering me-refresh data list, ListAdapter sangat bagus digunakan karena update-nya adaptif dan efisien secara resource.

    Dengan berbagai keunggulan dari ListAdapter apakah kamu masih setia dengan notifyDatasetChanged atau berminat merubah Adapter mu ke ListAdapter ?

    Referensi :

    https://www.educative.io/answers/diffutil-vs-notifydatasetchanged

    https://www.linkedin.com/posts/onkar-bhandari-339512188_androiddev-kotlin-tips-activity-7326587494261510146-HgK8

    https://hackernoon.com/3-quick-ways-to-optimize-recyclerview

    Published by Ahmad Saifur Ridlo

    Android Developer at Algostudio.net