0Pricing
Android Academy · Pelajaran

Adapter & DiffUtil

Buat adapter daftar yang efisien dan dilengkapi animasi dengan ListAdapter serta DiffUtil.ItemCallback. Gunakan submitList() untuk pembaruan parsial yang mulus.

Adapter & DiffUtil adalah pelajaran Android Academy gratis di CoddyKit. Ini adalah pelajaran 6 dari 6. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Android Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Android Academy mencakup 6 pelajaran total.

Masalah dengan notifyDataSetChanged()

Saat data RecyclerView berubah, pemanggilan notifyDataSetChanged() menggambar ulang setiap item — termasuk item yang tidak berubah. Hal ini menyebabkan:

  • Tidak ada animasi item
  • Kinerja buruk pada daftar berukuran besar
  • UI berkedip

DiffUtil menghitung rangkaian perubahan minimum dan menerapkannya dengan animasi yang halus.

DiffUtil.ItemCallback

Tentukan cara membandingkan dua item. Ada dua pertanyaan yang perlu dijawab:

  • areItemsTheSame — apakah keduanya mewakili item logis yang sama? (biasanya dengan membandingkan ID)
  • areContentsTheSame — apakah semua bidang yang terlihat identik?
data class Post(val id: Int, val title: String, val likes: Int)

class PostDiffCallback : DiffUtil.ItemCallback<Post>() {

    override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
        return oldItem.id == newItem.id   // same post?
    }

    override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
        return oldItem == newItem         // data class equality (all fields)
    }
}

ListAdapter

ListAdapter adalah RecyclerView.Adapter dengan DiffUtil bawaan. Teruskan ItemCallback ke konstruktornya dan panggil submitList() untuk memperbarui data:

class PostAdapter : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {

    inner class ViewHolder(val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val post = getItem(position)   // use getItem(), not a manual list
        holder.binding.tvTitle.text = post.title
        holder.binding.tvLikes.text = "${post.likes} likes"
    }
}

Mengirimkan Daftar Baru

Panggil submitList() setiap kali data berubah. ListAdapter membandingkan daftar lama dan baru di thread latar belakang, lalu hanya menerapkan perubahan yang diperlukan:

// In Activity or Fragment:
val adapter = PostAdapter()
binding.recyclerView.adapter = adapter

// Observe ViewModel LiveData:
viewModel.posts.observe(viewLifecycleOwner) { posts ->
    adapter.submitList(posts)   // diff calculated off main thread
}

// Important: always submit a NEW list, not a mutated old one
// WRONG:  currentList.add(newPost); adapter.submitList(currentList)
// RIGHT:  adapter.submitList(currentList + newPost)

Pendengar Klik di ListAdapter

Teruskan penangan klik melalui konstruktor — pola yang umum dan bersih:

class PostAdapter(
    private val onLike: (Post) -> Unit,
    private val onOpen: (Post) -> Unit
) : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val post = getItem(position)
        holder.binding.tvTitle.text = post.title
        holder.binding.btnLike.setOnClickListener { onLike(post) }
        holder.itemView.setOnClickListener { onOpen(post) }
    }
}

// Usage:
val adapter = PostAdapter(
    onLike = { viewModel.likePost(it) },
    onOpen = { navigateToDetail(it.id) }
)

getChangePayload() — Pembaruan Sebagian

Saat hanya satu bidang yang berubah (misalnya jumlah suka), hindari pengikatan ulang penuh dengan getChangePayload():

class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
    override fun areItemsTheSame(oldItem: Post, newItem: Post) = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: Post, newItem: Post) = oldItem == newItem

    override fun getChangePayload(oldItem: Post, newItem: Post): Any? {
        // Return non-null payload to trigger partial bind
        return if (oldItem.likes != newItem.likes) "likes_changed" else null
    }
}

// In Adapter.onBindViewHolder with payloads:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {
    if (payloads.contains("likes_changed")) {
        holder.binding.tvLikes.text = "${getItem(position).likes} likes"
    } else {
        super.onBindViewHolder(holder, position, payloads)
    }
}

Beberapa Jenis Tampilan

Gunakan getItemViewType() untuk menampilkan tata letak item yang berbeda dalam daftar yang sama:

class FeedAdapter : ListAdapter<FeedItem, RecyclerView.ViewHolder>(FeedDiffCallback()) {

    companion object {
        const val TYPE_POST = 0
        const val TYPE_AD   = 1
    }

    override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is FeedItem.Post -> TYPE_POST
            is FeedItem.Ad   -> TYPE_AD
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
        TYPE_POST -> PostViewHolder(ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        else      -> AdViewHolder(ItemAdBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }
}

ConcatAdapter — Menggabungkan Daftar

ConcatAdapter menggabungkan beberapa adaptor menjadi satu RecyclerView — sangat cocok untuk header, footer, dan status pemuatan:

// Separate adapters for different sections
val headerAdapter = HeaderAdapter()
val postAdapter   = PostAdapter()
val loaderAdapter = LoadingAdapter()

// Combine them in order
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, postAdapter, loaderAdapter)

// Update each independently
postAdapter.submitList(posts)
loaderAdapter.showLoading(true)

AsyncListDiffer — Kontrol Manual

Jika Anda tidak dapat memperluas ListAdapter, gunakan AsyncListDiffer secara langsung:

class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    private val differ = AsyncListDiffer(this, PostDiffCallback())

    fun submitList(list: List<Post>) = differ.submitList(list)
    fun getItem(position: Int): Post = differ.currentList[position]

    override fun getItemCount() = differ.currentList.size
}

Kiat Kinerja

Jaga agar RecyclerView tetap cepat:

  • Gunakan setHasStableIds(true) jika item Anda memiliki ID unik dan stabil
  • Hindari membuat objek di dalam onBindViewHolder
  • Gunakan RecycledViewPool untuk berbagi tampilan yang didaur ulang di beberapa RecyclerView
  • Gunakan setItemViewCacheSize() untuk memperbesar tembolok di luar layar

Animasi Tersedia Secara Otomatis

Integrasi DiffUtil pada ListAdapter secara otomatis memicu DefaultItemAnimator untuk:

  • Efek muncul perlahan untuk item baru
  • Efek menghilang perlahan untuk item yang dihapus
  • Animasi perpindahan saat posisi item berubah

Tetapkan animator khusus: recyclerView.itemAnimator = MyAnimator(). Tetapkan null untuk menonaktifkan semua animasi.

Pemeriksaan Singkat

Metode mana yang harus Anda implementasikan dalam DiffUtil.ItemCallback?

Ringkasan: Adaptor & DiffUtil

Daftar yang efisien dan memiliki animasi dengan kode minimal:

  • DiffUtil.ItemCallback — definisikan identitas (areItemsTheSame) dan kesetaraan (areContentsTheSame)
  • ListAdapter — perbandingan perbedaan secara asinkron bawaan; panggil submitList()
  • Selalu kirimkan referensi daftar baru, jangan pernah mengubah daftar saat ini
  • Gunakan getChangePayload() untuk pengikatan ulang sebagian
  • Gunakan ConcatAdapter untuk menggabungkan beberapa adaptor

Berikutnya: navigasi antarlayar dengan Navigation Component.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Adapter & DiffUtil” gratis?

Ya — teks lengkap “Adapter & DiffUtil” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Android Academy, upgrade ke CoddyKit PRO. Kursus Android Academy mencakup 6 pelajaran total.

Apa yang akan aku pelajari di “Adapter & DiffUtil”?

Buat adapter daftar yang efisien dan dilengkapi animasi dengan ListAdapter serta DiffUtil.ItemCallback. Gunakan submitList() untuk pembaruan parsial yang mulus. Kamu berlatih Android Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Android Academy?

Tidak diperlukan pengalaman sebelumnya. Android Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 6 dari 6.

Berapa lama pelajaran “Adapter & DiffUtil” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Android Academy ini?

Ya. Setiap pelajaran Android Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Koleksi Kotlin
  2. Dasar RecyclerView
  3. Peristiwa Klik RecyclerView
  4. SharedPreferences
  5. Preferensi DataStore
  6. Adapter & DiffUtil
← Kembali ke Android Academy