Android
RecyclerView
Item Addition
Item Removal
Tutorial

Android RecyclerView addition removal of items

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Adding and removing items in a RecyclerView seems simple until you need smooth animations, consistent state, and reliable behavior under rapid updates. Many bugs come from mixing data mutation with incorrect notify calls or from using position-based logic after the list changed. A robust solution keeps one source of truth and lets DiffUtil calculate minimal UI updates.

Core Sections

1. Choose an update model early

You can update RecyclerView in two broad ways:

  • Manual mutable list with targeted notify calls.
  • Immutable list snapshots through ListAdapter and DiffUtil.

Manual updates are fine for very small cases, but they are error-prone when insertions, removals, and moves happen frequently. ListAdapter is usually the safer production default because it computes changes consistently.

Define stable identity and content comparison in a DiffUtil.ItemCallback.

kotlin
1data class RowItem(val id: Long, val label: String, val done: Boolean)
2
3class RowDiff : DiffUtil.ItemCallback<RowItem>() {
4    override fun areItemsTheSame(oldItem: RowItem, newItem: RowItem): Boolean {
5        return oldItem.id == newItem.id
6    }
7
8    override fun areContentsTheSame(oldItem: RowItem, newItem: RowItem): Boolean {
9        return oldItem == newItem
10    }
11}
12
13class RowAdapter : ListAdapter<RowItem, RowViewHolder>(RowDiff())

Then modify state in a view model and submit a new list snapshot.

kotlin
1fun addItem(current: List<RowItem>, item: RowItem): List<RowItem> = current + item
2
3fun removeItem(current: List<RowItem>, id: Long): List<RowItem> =
4    current.filterNot { it.id == id }

This avoids manual position math in the adapter.

3. Manual notify pattern when ListAdapter is not used

If you use a mutable list directly, mutate data first, then call the exact notify method once.

kotlin
1class LegacyAdapter : RecyclerView.Adapter<RowViewHolder>() {
2    private val items = mutableListOf<RowItem>()
3
4    fun add(item: RowItem) {
5        items.add(item)
6        notifyItemInserted(items.lastIndex)
7    }
8
9    fun removeAt(index: Int) {
10        if (index !in items.indices) return
11        items.removeAt(index)
12        notifyItemRemoved(index)
13    }
14}

Do not call notifyDataSetChanged after targeted notify calls, because that defeats animations and can hide adapter bugs.

4. Handle user actions and background updates safely

Real apps receive updates from multiple sources, such as user taps, sync jobs, and pagination. Keep updates in one state owner, typically a view model. The adapter should only render state, not own business mutations.

Recommended flow:

  1. User action emits intent.
  2. View model updates list state.
  3. UI observes state and submits list.
  4. Adapter renders diff.

This model reduces race conditions where UI and data source disagree.

5. Preserve correctness with stable identifiers

Never depend on adapter position as identity. Position changes after insertions and removals. Use stable IDs from data objects and pass IDs through click handlers.

If you need animated moves or partial updates, stable identity becomes even more important. Inconsistent identity rules are a major source of duplicate-row and wrong-row update bugs.

6. Testing and diagnostics

Add tests that cover:

  • remove first, middle, and last item
  • add multiple items quickly
  • remove item while async refresh arrives
  • repeated add and remove of same ID

For debugging, log list size and changed IDs at each state transition. When a visual mismatch occurs, these logs usually reveal whether the issue is state generation or adapter rendering.

Common Pitfalls

  • Mutating list data after submit and expecting DiffUtil to detect changes correctly.
  • Using adapter position as item identity in callbacks.
  • Calling broad refresh methods instead of precise update pathways.
  • Mixing business logic inside adapter methods.
  • Forgetting boundary checks when removing by index in legacy adapters.

Summary

  • Prefer ListAdapter and DiffUtil for predictable add and remove behavior.
  • Keep list mutation in view model state, not inside RecyclerView adapter code.
  • Use stable IDs as identity and avoid position-based assumptions.
  • Apply targeted update notifications only when using manual mutable adapters.
  • Add state-transition tests to catch race conditions before release.

Course illustration
Course illustration

All Rights Reserved.