material-components/material-components-android · error · IllegalArgumentException

> resourceName < is not a valid ancestor

Error message

> resourceName < is not a valid ancestor

What it means

TransitionUtils.findAncestorById walks up from a given view's parent chain looking for a view whose id equals ancestorId; if the root is reached without a match it throws IllegalArgumentException(resourceName + ' is not a valid ancestor'). MaterialContainerTransform calls this (MaterialContainerTransform.java:920) with its drawingViewId to locate the view whose overlay the transform should draw in — so the drawing view MUST be an ancestor of the transitioning start/end view in the current hierarchy.

Source

Thrown at lib/java/com/google/android/material/transition/platform/TransitionUtils.java:290

      return descendant;
    }
    return findAncestorById(view, viewId);
  }

  static View findAncestorById(View view, @IdRes int ancestorId) {
    String resourceName = view.getResources().getResourceName(ancestorId);
    while (view != null) {
      if (view.getId() == ancestorId) {
        return view;
      }
      ViewParent parent = view.getParent();
      if (parent instanceof View) {
        view = (View) parent;
      } else {
        break;
      }
    }
    throw new IllegalArgumentException(resourceName + " is not a valid ancestor");
  }

  static RectF getRelativeBounds(View view) {
    return new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
  }

  static Rect getRelativeBoundsRect(View view) {
    return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
  }

  static RectF getLocationInWindow(View view) {
    int[] location = new int[2];
    view.getLocationInWindow(location);
    int left = location[0];
    int top = location[1];
    int right = left + view.getWidth();
    int bottom = top + view.getHeight();
    return new RectF(left, top, right, bottom);

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Ensure drawingViewId points to a real ancestor: use the id of the parent layout that contains BOTH start and end views, or android.R.id.content only when the views live in the activity content view.
  2. For DialogFragment-based transforms, set the drawing view to the dialog's decor/content container (or the dialog's own root id), not the activity's content id.
  3. Cancel or defer the transition if the start/end view gets detached (guard with a TransitionListener and lifecycle checks) so findAncestor never runs against a stale hierarchy.
  4. Verify ids are unique across the inflated layouts — a duplicate id on a non-ancestor can mask the real ancestor during the walk.

Example fix

// before
MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.detail_card); // sibling, not ancestor -> throws

// after
MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.root_content); // ancestor of both start and end views
Defensive patterns

Strategy: validation

Validate before calling

// Walk up from the shared element and confirm drawingViewId is on its parent
// chain BEFORE scheduling the container transform.
static boolean isAncestorById(View view, @IdRes int ancestorId) {
  for (View v = view; v != null; ) {
    if (v.getId() == ancestorId) return true;
    ViewParent p = v.getParent();
    v = (p instanceof View) ? (View) p : null;
  }
  return false;
}

// usage
if (isAncestorById(sharedElement, transform.getDrawingViewId())) {
  TransitionManager.beginDelayedTransition(root, transform);
} else {
  transform.setDrawingViewId(android.R.id.content); // known ancestor fallback
}

Try / catch

try {
  fragment.setSharedElementEnterTransition(transform);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().endsWith("is not a valid ancestor")) {
    transform.setDrawingViewId(android.R.id.content);
    fragment.setSharedElementEnterTransition(transform); // retry with real ancestor
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: MaterialContainerTransform.setDrawingViewId(id) set to an id that is a sibling, a descendant, or a view in a different window/Fragment hierarchy than the transitioned view; using android.R.id.content or a container id that is not on the parent chain of the shared element; view hierarchy rebuilt between scheduling and running the transition (e.g., RecyclerView recycle or Fragment view destroyed), so the id no longer sits above the view.

Common situations: Container transform shared-element transitions where drawingViewId defaults to android.R.id.content but the transitioned view lives in a DialogFragment or different window; setting the drawing view to the Fragment's inner container while the shared element is in a parent layout; running the transition after the view was detached and re-attached elsewhere.

Related errors


AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14). Data as JSON: /api/errors/38543f52f3dff0e1. Report an issue: GitHub.