{"record":{"id":"38543f52f3dff0e1","repo":"material-components/material-components-android","slug":"resourcename-is-not-a-valid-ancestor-38543f","errorCode":null,"errorMessage":"> resourceName < is not a valid ancestor","messagePattern":"> resourceName < is not a valid ancestor","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/transition/platform/TransitionUtils.java","lineNumber":290,"sourceCode":"      return descendant;\n    }\n    return findAncestorById(view, viewId);\n  }\n\n  static View findAncestorById(View view, @IdRes int ancestorId) {\n    String resourceName = view.getResources().getResourceName(ancestorId);\n    while (view != null) {\n      if (view.getId() == ancestorId) {\n        return view;\n      }\n      ViewParent parent = view.getParent();\n      if (parent instanceof View) {\n        view = (View) parent;\n      } else {\n        break;\n      }\n    }\n    throw new IllegalArgumentException(resourceName + \" is not a valid ancestor\");\n  }\n\n  static RectF getRelativeBounds(View view) {\n    return new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());\n  }\n\n  static Rect getRelativeBoundsRect(View view) {\n    return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());\n  }\n\n  static RectF getLocationInWindow(View view) {\n    int[] location = new int[2];\n    view.getLocationInWindow(location);\n    int left = location[0];\n    int top = location[1];\n    int right = left + view.getWidth();\n    int bottom = top + view.getHeight();\n    return new RectF(left, top, right, bottom);","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/transition/platform/TransitionUtils.java#L272-L308","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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.","Verify ids are unique across the inflated layouts — a duplicate id on a non-ancestor can mask the real ancestor during the walk."],"exampleFix":"// before\nMaterialContainerTransform transform = new MaterialContainerTransform();\ntransform.setDrawingViewId(R.id.detail_card); // sibling, not ancestor -> throws\n\n// after\nMaterialContainerTransform transform = new MaterialContainerTransform();\ntransform.setDrawingViewId(R.id.root_content); // ancestor of both start and end views","handlingStrategy":"validation","validationCode":"// Walk up from the shared element and confirm drawingViewId is on its parent\n// chain BEFORE scheduling the container transform.\nstatic boolean isAncestorById(View view, @IdRes int ancestorId) {\n  for (View v = view; v != null; ) {\n    if (v.getId() == ancestorId) return true;\n    ViewParent p = v.getParent();\n    v = (p instanceof View) ? (View) p : null;\n  }\n  return false;\n}\n\n// usage\nif (isAncestorById(sharedElement, transform.getDrawingViewId())) {\n  TransitionManager.beginDelayedTransition(root, transform);\n} else {\n  transform.setDrawingViewId(android.R.id.content); // known ancestor fallback\n}","typeGuard":null,"tryCatchPattern":"try {\n  fragment.setSharedElementEnterTransition(transform);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().endsWith(\"is not a valid ancestor\")) {\n    transform.setDrawingViewId(android.R.id.content);\n    fragment.setSharedElementEnterTransition(transform); // retry with real ancestor\n  } else {\n    throw e;\n  }\n}","preventionTips":["Choose drawingViewId as a layout container that physically contains BOTH start and end views.","For dialogs, use the dialog's own content container id, not the activity's content id.","Cancel scheduled transforms in onDestroyView so they never run against a rebuilt hierarchy.","Keep view ids unique across navigation graph layouts."],"tags":["android","material-components","transition","container-transform","view-hierarchy"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}