DrKLO/Telegram · critical · RuntimeException
Holder at{i} {holder} ...
Error message
Holder at{i} {holder}
... What it means
In dispatchLayoutStep3 (the animation phase of layout), RecyclerView wraps the entire change-matching loop in try/catch. If ANY exception is thrown while recording post-layout info or matching changed ViewHolders, it rethrows a RuntimeException whose message is a dump of every attached, non-ignored ViewHolder with its index. This is a diagnostic wrapper: the real failure is the chained cause (Throwable e), not the holder list itself. The list is provided so the developer can inspect the live view-holder state at the moment of failure.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java:4210
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
} catch (Exception e) {
StringBuilder builder = new StringBuilder();
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder == null || holder.shouldIgnore()) {
continue;
}
builder.append("Holder at" + i + " " + holder + "\n");
}
throw new RuntimeException(builder.toString(), e);
}
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mDispatchItemsChangedEvent = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {View on GitHub (pinned to 45ab8f4308)
Solutions
- Inspect the chained cause (RuntimeException.getCause()) — that is the real failure; the holder dump is only context. Fix the underlying defect the cause names (duplicate stable ID, invalid position, NPE in custom animator).
- If the cause points to a custom ItemAnimator, audit its recordPostLayoutInformation/animateChange implementations for null checks and for touching holders that may have been recycled.
- Ensure all adapter mutations happen on the main thread and are never concurrent with a layout pass; route mutations through a single-threaded queue or DiffUtil.
- Temporarily disable item animations (recyclerView.setItemAnimator(null)) to confirm whether the crash is animation-path-only; if it disappears, the ItemAnimator or an inconsistent-change notification is the culprit.
Example fix
// before: a custom ItemAnimator throwing during change animation
@Override public boolean animateChange(@NonNull RecyclerView.ViewHolder oldVh,
@NonNull RecyclerView.ViewHolder newVh,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
return animateMove(newVh, preInfo.left, preInfo.top, postInfo.left, postInfo.top); // NPE if preInfo null
}
// after: guard against null info and fall back to a no-op change
@Override public boolean animateChange(@NonNull RecyclerView.ViewHolder oldVh,
@NonNull RecyclerView.ViewHolder newVh,
@Nullable ItemHolderInfo preInfo, @Nullable ItemHolderInfo postInfo) {
if (preInfo == null || postInfo == null) {
dispatchChangeFinished(newVh, false);
return false;
}
return animateMove(newVh, preInfo.left, preInfo.top, postInfo.left, postInfo.top);
} Defensive patterns
Strategy: try-catch
Try / catch
// This is a framework-thrown wrapper; catch only at a top-level uncaught-exception handler
// to report diagnostics, then FIX the chained cause rather than suppress it.
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
if (e instanceof RuntimeException && e.getMessage() != null
&& e.getMessage().startsWith("Holder at")) {
reportToCrashAnalytics(e.getCause() != null ? e.getCause() : e);
}
}); Prevention
- Treat this as a symptom, not the bug — always read RuntimeException.getCause().
- Keep item animations off critical data-change paths until the underlying inconsistency is fixed.
- Stress-test adapter updates (rapid insert/remove) under animations to surface latent inconsistencies early.
When it happens
Trigger: An exception thrown inside ItemAnimator.recordPostLayoutInformation, animateChange, or animateMove/Remove during Step 3; or a downstream failure in mViewInfoStore operations. Typically caused by a custom ItemAnimator that throws, by the same duplicate-key/invalid-position defects (see errors 42/43/49) surfacing during animation, or by a ViewHolder being modified concurrently.
Common situations: Custom ItemAnimator with a null/buggy implementation; concurrent adapter mutation from a background thread that invalidates holders mid-animation; an ItemAnimator touching a recycled view; a prior inconsistent notify sequence that only surfaces corruption during Step 3.
Related errors
- only remove and update ops can be dispatched in first pass
- Providing a LayoutTransition into RecyclerView is not suppor
- should not dispatch add or move for pre layout
- op should be remove or update.{}
- Unknown update op type for {}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/0c6daea05903469d.
Report an issue: GitHub.