RecyclerView, DiffUtil & XML Layouts Quiz
UI › Views & RecyclerView
Which component stores scrapped ViewHolders so they can be reused instead of re-inflated?
- The RecycledViewPool
- The LayoutManager
- The ItemAnimator
- The Adapter's internal cache
Answer: The RecycledViewPool
Offscreen ViewHolders are scrapped into the RecycledViewPool, keyed by view type. When a new item scrolls in, RecyclerView rebinds a pooled holder instead of inflating a new one.
Your list updates by calling notifyDataSetChanged() after every change. What is the main cost?
- Every visible holder is rebound and item animations are lost
- The adapter leaks its previous list
- The RecyclerView re-inflates all item views from XML
- The LayoutManager is recreated on each call
Answer: Every visible holder is rebound and item animations are lost
notifyDataSetChanged() invalidates everything: all visible items rebind, and RecyclerView can't tell what moved, so it can't animate or skip work. Views are still recycled (not necessarily re-inflated), but the granular notify/DiffUtil path does strictly less work.
In a DiffUtil callback, an old and new entry return true from areItemsTheSame() and false from areContentsTheSame(). What does RecyclerView do?
- Rebinds that item (a change, optionally with a payload)
- Removes the old item and inserts the new one
- Moves the item to its new position
- Nothing, the pair is treated as identical
Answer: Rebinds that item (a change, optionally with a payload)
Same identity + different contents = a changed item. RecyclerView dispatches a change notification for it, and getChangePayload() can narrow the rebind to just the changed part.
What is the purpose of getChangePayload() in DiffUtil?
- Describe what changed so onBindViewHolder can do a partial rebind
- Decide whether two items are the same logical entity
- Serialize the item for the RecycledViewPool
- Tell the LayoutManager how far the item moved
Answer: Describe what changed so onBindViewHolder can do a partial rebind
The payload flows into onBindViewHolder(holder, position, payloads). Instead of rebinding the whole row you can update only the field that changed, e.g. a like count, avoiding image reloads and text relayout.
Where should an item's click listener be attached, and how should it find the clicked item?
- In onCreateViewHolder, reading bindingAdapterPosition when the click fires
- In onBindViewHolder, capturing the bound position in the lambda
- In onViewAttachedToWindow, capturing the item reference
- On the RecyclerView itself, using the touch coordinates
Answer: In onCreateViewHolder, reading bindingAdapterPosition when the click fires
Attaching once at create time avoids allocating a new listener on every bind. Reading bindingAdapterPosition at click time keeps it correct after moves; a position captured at bind time can be stale.
Why must bindingAdapterPosition be checked against RecyclerView.NO_POSITION before use?
- The holder may be clicked mid-update, after its item was removed
- The position is only valid inside onBindViewHolder
- NO_POSITION is returned for every item below the fold
- It is only set once the item animator finishes its first pass
Answer: The holder may be clicked mid-update, after its item was removed
During updates and animations a holder can be detached from any adapter position (e.g. its item was just removed). Using -1 (NO_POSITION) as an index would crash or act on the wrong item.
What is the recommended way to add equal spacing between all items in a list?
- An ItemDecoration overriding getItemOffsets()
- layout_margin on the item's root view
- Padding on the RecyclerView with clipChildren=false
- An invisible spacer view appended after each item
Answer: An ItemDecoration overriding getItemOffsets()
getItemOffsets() adds space outside each item without touching the item layout. It can vary by position (e.g. no top gap for the first row), composes with other decorations, and is reusable across screens.
Why do nested LinearLayouts with layout_weight hurt measure performance?
- Weights force extra measure passes that multiply with nesting (double taxation)
- Weights disable the view cache for the whole subtree
- LinearLayout always draws its children twice
- Weighted children are measured on a background thread and synced back
Answer: Weights force extra measure passes that multiply with nesting (double taxation)
A weighted LinearLayout measures children once to get desired sizes and again to distribute remaining space. Nest two of them and the inner children get measured 4 times; each level multiplies. In a list row this happens for every bound item.
What does the <merge> tag do in an XML layout?
- Removes a redundant wrapper ViewGroup when the layout is included in a parent
- Combines two layout files into one at build time
- Lets two views share the same layout parameters
- Marks a subtree to be inflated lazily on first use
Answer: Removes a redundant wrapper ViewGroup when the layout is included in a parent
When a <merge> root is inflated into a parent (via <include> or a custom ViewGroup inflating itself), its children attach directly to that parent, eliminating one level of nesting that would otherwise exist just to satisfy the single-root rule.
When is ViewStub the right tool?
- For rarely-shown UI, so its subtree is only inflated on first use
- For list items that need a second view type
- For deferring all inflation until after the first frame
- For sharing one view instance between two screens
Answer: For rarely-shown UI, so its subtree is only inflated on first use
A ViewStub is a zero-size placeholder; calling inflate() (or setting it visible) swaps in the real layout. Error banners, empty states and other sometimes-UI cost nothing until actually needed.
Where does ListAdapter (AsyncListDiffer) run the diff computation triggered by submitList()?
- On a background thread, then applies updates on the main thread
- On the main thread, synchronously before returning
- On the RenderThread alongside draw commands
- Inside onBindViewHolder for the visible range only
Answer: On a background thread, then applies updates on the main thread
The Myers diff can be O(N) to O(N^2)-ish work; AsyncListDiffer moves it off the main thread and posts the resulting granular notifications back, so large list swaps don't jank the frame.
What does recyclerView.setHasFixedSize(true) actually promise?
- The RecyclerView's own size won't change when adapter contents change
- Every item view has exactly the same height
- The adapter's item count never changes
- The pool should never evict holders for this list
Answer: The RecyclerView's own size won't change when adapter contents change
It is about the RecyclerView's dimensions, not item sizes: if adapter changes can't resize the RecyclerView (e.g. it is match_parent), it can skip requesting layout on its parent when items change, saving a layout pass.
Which of these in onBindViewHolder is the classic scroll-jank mistake?
- Creating a SimpleDateFormat and formatting a date on every bind
- Setting text on a TextView from the bound item
- Reading a field from the already-loaded list item
- Calling holder.itemView.tag to store the item id
Answer: Creating a SimpleDateFormat and formatting a date on every bind
Bind runs for every row as you fling; per-bind allocations (formatters, listeners, bitmaps) and CPU-heavy work multiply into dropped frames. Precompute display strings in the model/mapper, or cache formatters.
A vertical list contains horizontal carousels (RecyclerView inside RecyclerView). What is the standard optimisation?
- Share one RecycledViewPool across the inner lists
- Give every inner list setHasFixedSize(false)
- Replace the inner lists with nested ScrollViews
- Call notifyDataSetChanged() on outer scroll to refresh carousels
Answer: Share one RecycledViewPool across the inner lists
Each inner RecyclerView otherwise keeps its own pool, re-inflating carousel cells as rows recycle. A shared pool lets a scrolled-away row's cells be reused by the next row's carousel. Also set the inner LinearLayoutManager's recycleChildrenOnDetach.