RecyclerView with DiffUtil for efficient lists

Alex Chen Jan 2026
2 tabs
package com.example.myapp.ui.adapters

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.myapp.databinding.ItemPostBinding
import com.example.myapp.models.Post

class PostsAdapter(
    private val onPostClick: (Post) -> Unit,
    private val onLikeClick: (Post) -> Unit = {}
) : ListAdapter<Post, PostsAdapter.PostViewHolder>(PostDiffCallback()) {

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

    override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    class PostViewHolder(
        private val binding: ItemPostBinding,
        private val onPostClick: (Post) -> Unit,
        private val onLikeClick: (Post) -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(post: Post) {
            binding.apply {
                titleText.text = post.title
                bodyText.text = post.body
                authorText.text = post.author
                likesText.text = "${post.likesCount} likes"
                timestampText.text = formatTimestamp(post.createdAt)

                likeButton.setImageResource(
                    if (post.isLiked) R.drawable.ic_liked else R.drawable.ic_like
                )

                root.setOnClickListener { onPostClick(post) }
                likeButton.setOnClickListener { onLikeClick(post) }
            }
        }

        private fun formatTimestamp(timestamp: Long): String {
            val now = System.currentTimeMillis()
            val diff = now - timestamp
            val seconds = diff / 1000
            val minutes = seconds / 60
            val hours = minutes / 60
            val days = hours / 24

            return when {
                days > 0 -> "$days days ago"
                hours > 0 -> "$hours hours ago"
                minutes > 0 -> "$minutes minutes ago"
                else -> "Just now"
            }
        }
    }

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

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

        override fun getChangePayload(oldItem: Post, newItem: Post): Any? {
            return if (oldItem.likesCount != newItem.likesCount ||
                       oldItem.isLiked != newItem.isLiked) {
                "LIKES_CHANGED"
            } else {
                null
            }
        }
    }
}
2 files · kotlin Explain with highlit

RecyclerView displays large datasets efficiently with view recycling. I create a ViewHolder to cache view references and an Adapter to bind data. DiffUtil calculates minimal updates using areItemsTheSame and areContentsTheSame callbacks, improving performance over notifyDataSetChanged(). ListAdapter simplifies DiffUtil usage with background diffing. ItemTouchHelper adds swipe and drag gestures. ConcatAdapter combines multiple adapters. Layout managers control positioning—LinearLayoutManager for lists, GridLayoutManager for grids, StaggeredGridLayoutManager for Pinterest-style layouts. ViewBinding eliminates findViewById. Pagination libraries load data incrementally. RecyclerView powers most scrolling lists in Android apps.