DrKLO/Telegram · critical · IllegalArgumentException

called detach on an already detached child {vh}

Error message

called detach on an already detached child {vh}

What it means

The symmetric guard in detachViewFromParent: if the child's ViewHolder is ALREADY tmp-detached and is not shouldIgnore, RecyclerView throws 'called detach on an already detached child'. Detaching twice leaves the detach bookkeeping inconsistent and would orphan the view from the layout pass.

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java:984

                final ViewHolder vh = getChildViewHolderInt(child);
                if (vh != null) {
                    if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
                        throw new IllegalArgumentException("Called attach on a child which is not"
                                + " detached: " + vh + exceptionLabel());
                    }
                    vh.clearTmpDetachFlag();
                }
                RecyclerView.this.attachViewToParent(child, index, layoutParams);
            }

            @Override
            public void detachViewFromParent(int offset) {
                final View view = getChildAt(offset);
                if (view != null) {
                    final ViewHolder vh = getChildViewHolderInt(view);
                    if (vh != null) {
                        if (vh.isTmpDetached() && !vh.shouldIgnore()) {
                            throw new IllegalArgumentException("called detach on an already"
                                    + " detached child " + vh + exceptionLabel());
                        }
                        vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
                    }
                }
                RecyclerView.this.detachViewFromParent(offset);
            }

            @Override
            public void onEnteredHiddenState(View child) {
                final ViewHolder vh = getChildViewHolderInt(child);
                if (vh != null) {
                    vh.onEnteredHiddenState(RecyclerView.this);
                }
            }

            @Override
            public void onLeftHiddenState(View child) {

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Track detached state explicitly and never call detach twice without an attach in between.
  2. Prefer recycler.recycleView / detachAndScrapView over raw detachViewFromParent.
  3. Guard each detach with a check against the child's current state.

Example fix

// before
layoutManager.detachViewAt(i); // may already be detached

// after
ViewHolder vh = recyclerView.getChildViewHolder(child);
if (vh == null || !vh.isTmpDetached()) {
    layoutManager.detachViewAt(i);
}
Defensive patterns

Strategy: validation

Validate before calling

View child = getChildAt(i);
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
if (vh == null || !vh.isTmpDetached()) {
    detachViewFromParent(i);
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: A custom LayoutManager calls detachViewFromParent twice on the same child without an intervening attach. A scrap/recycle routine that detaches a view already held in a detached scrap.

Common situations: Custom LayoutManager with faulty bookkeeping during onLayoutChildren. Misuse of detachAndScrapView vs detachViewFromParent. Predictive animation path that detaches a view the layout already detached.

Related errors


AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14). Data as JSON: /api/errors/45e88a33c93847a3. Report an issue: GitHub.