DrKLO/Telegram · critical · IllegalArgumentException

Called attach on a child which is not detached: {vh}

Error message

Called attach on a child which is not detached: {vh}

What it means

RecyclerView's internal LayoutManager proxy implements attachViewToParent. Before attaching a child it checks the child's ViewHolder: if the holder is NOT marked tmp-detached and does not carry the shouldIgnore flag, it throws 'Called attach on a child which is not detached'. Attaching a view that the ViewGroup still considers attached would corrupt the view tree.

Source

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

                    // detaching when being removed. If a child is re-added before the
                    // lazy detach occurs, it will receive invalid attach/detach sequencing.
                    child.clearAnimation();
                }
                RecyclerView.this.removeAllViews();
            }

            @Override
            public ViewHolder getChildViewHolder(View view) {
                return getChildViewHolderInt(view);
            }

            @Override
            public void attachViewToParent(View child, int index,
                    ViewGroup.LayoutParams layoutParams) {
                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);

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Ensure every attachViewToParent call is preceded by a matching detachViewFromParent (or removeView) for the same child.
  2. Use the Recycler API (recycler.getViewForPosition) instead of manual attach when possible.
  3. In onLayoutChildren, follow detach-all -> fill -> reattach pattern strictly; never attach a view you did not detach.

Example fix

// before (custom LM)
detachView(child);            // forgot for some path
attachViewToParent(child, 0, lp);

// after
// only attach views you previously detached in this layout pass
if (isDetached(child)) {
    attachViewToParent(child, 0, lp);
}
Defensive patterns

Strategy: validation

Validate before calling

// In a custom LayoutManager, only attach children you have detached in this pass
View child = getChildAt(i);
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
if (vh != null && (vh.isTmpDetached() || vh.shouldIgnore())) {
    attachViewToParent(child, index, lp);
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: A custom LayoutManager calls layoutManager.attachViewToParent / addView on a child that was never detached via the matching detach path. A mismatched attach/detach sequence in onLayoutChildren (e.g. attach called twice, or attach without a prior detachView).

Common situations: Hand-rolled LayoutManager that manages its own scrap recycling and forgets to detach before re-attaching. Off-by-one in recycler/scrap bookkeeping where a still-attached view is reattached. Recycler reuse race during predictive animations.

Related errors


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