Sparround

RecyclerView, ViewBinding, ConstraintLayout — performance

RecyclerView is the standard for large lists — its essence is view recycling: an item scrolled off screen returns its view to a pool for reuse (onBindViewHolder only binds data, no inflation).

Key parts:

  • ViewHolder — caches view references, avoiding repeated findViewById
  • DiffUtil / ListAdapter — diffs old/new lists and updates only changed items; bare notifyDataSetChanged() is the expensive antipattern
  • ItemDecoration / ItemAnimator — dividers and animations

ViewBinding generates a type-safe binding class per layout: no findViewById, null-safe, and unlike DataBinding it puts no logic in layouts (DataBinding expressions are discouraged in modern code).

ConstraintLayout — complex layouts with a flat hierarchy: it avoids the double measure passes nested LinearLayouts (especially with layout_weight) cause. Deep view hierarchies = slow measure/layout = dropped frames.

kotlin
class TransactionAdapter :
    ListAdapter<TransactionUi, TransactionAdapter.VH>(Diff) {

    object Diff : DiffUtil.ItemCallback<TransactionUi>() {
        override fun areItemsTheSame(a: TransactionUi, b: TransactionUi) = a.id == b.id
        override fun areContentsTheSame(a: TransactionUi, b: TransactionUi) = a == b
    }

    class VH(val binding: ItemTransactionBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = VH(
        ItemTransactionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    )

    override fun onBindViewHolder(holder: VH, position: Int) {
        val item = getItem(position)
        holder.binding.title.text = item.title
        holder.binding.amount.text = item.formattedAmount
    }
}

// İstifadə: adapter.submitList(newList) — diff arxa planda hesablanır

ListAdapter + DiffUtil — the modern list adapter

A frame for the performance question: (1) lists — ListAdapter/DiffUtil, setHasFixedSize(true), simpler item layouts, Coil/Glide for images (placeholder + sizing); (2) hierarchy — check depth in Layout Inspector, move to ConstraintLayout, use merge/ViewStub; (3) measurement — Profiler + Choreographer frame metrics. "What must onBindViewHolder never do?" — allocate objects, load images synchronously, re-wire listeners per bind.

🛠 Practice task

Build a list of 50+ items and measure three changes:

  • Update it with notifyDataSetChanged() first, then with ListAdapter + DiffUtil (submitList) — observe the animation and smoothness difference.
  • Allocate an object inside onBindViewHolder on purpose and watch allocations in the Profiler; then clean it up.
  • Check the item layout's depth in Layout Inspector and flatten a nested LinearLayout into ConstraintLayout.

Done when: you can answer "the list is janky" with a measure → fix → measure story.

📚 Sources and documentation