software-mansion / software-mansion/react-native-screens
Fabric/Android: views re-parented inside a screen crash with "View already has a parent" while its removal transition is active
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.7k
- Forks
- 714
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 71
Description
Description
On Fabric/Android, any view that Fabric re-parents inside a screen while that screen's removal transition is active crashes the app with addViewAt: ... View already has a parent. It is not limited to reorders, and it does not need removeClippedSubviews.
Screen.startTransitionRecursive marks the whole outgoing subtree with ViewGroup.startViewTransition (Screen.kt, via screen.post { startRemovalTransition() } in NativeProxy.kt). AOSP's ViewGroup.removeFromArray then deliberately keeps mParent set for views in mTransitioningViews. So when Fabric's mount batch removes such a view and re-inserts it under a different parent, the insert throws — and React Native's own recovery in SurfaceMountingManager.addViewAt calls removeView(), which is a no-op once the view is already out of the child array.
What we hit
Navigating replace() from one onboarding step to the next. The outgoing screen's CTA leaves its loading state in the same frame: the button's content View loses { opacity: 0 }, Fabric therefore flattens it, and the label Text is re-parented one level up to the button root. That move is the thing that explodes.
java.lang.IllegalStateException: addViewAt: cannot insert view [1604] into parent [1608]:
View already has a parent: [1606] Parent: ReactViewGroup View: ReactTextView
at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:343)
java.lang.IllegalStateException: addViewAt: failed to insert view [1604] into parent [1608] at index 0
at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:372)
at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(IntBufferBatchMountItem.kt:122)
at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems(MountItemDispatcher.kt:250)
at com.facebook.react.fabric.FabricUIManager$DispatchUIFrameCallback.doFrameGuarded(FabricUIManager.java:1622)
Caused by: java.lang.IllegalStateException: The specified child already has a parent.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5333)
at com.facebook.react.views.view.ReactClippingViewManager.addView(ReactClippingViewManager.kt:36)
The tree from SurfaceMountingManager's own dump, inside the screen being removed:
1608 RCTView <- button root (target parent)
|- 1606 RCTView <- current parent of 1604
| \- 1604 RCTText <- the label being moved up
\- 1664 AndroidProgressBar
100% reproducible for us, debug and release. iOS is unaffected — RNSScreen.mm preserves dismissed content with snapshotViewAfterScreenUpdates, so nothing is retained.
Why the existing PRs do not cover it
- #4492 detects a screen being moved and skips the removal transition. Ours is a genuine removal (
replace()really does remove the screen), so that condition would not fire. - #3249 / #3250 describe the same retention:
endTransitionRecursiveiteratesparent.children, so a view Fabric removed during the transition never receivesendViewTransitionand stays marked. - facebook/react-native#47634 fixed this AOSP behaviour only for the
removeClippedSubviewspath. HereReactClippingViewManager.addView:36is the plainparent.addView(child, index)branch.
Environment
react-native-screens4.28.0react-native0.87.1, New Architecture (Fabric + bridgeless), Hermesreact-native-reanimated4.6.0 — ruled out: removing the screen'sentering/exitinglayout animations, and replacingAnimated.Viewwith plainView, changes nothing- Xiaomi 13, Android 16
Workaround
Pinning the moved view with collapsable={false} so it can never be flattened, and therefore never moved, removes the crash. That only covers the flattening trigger — a sibling reorder inside a removing screen would still hit it.
Possible directions
Either end the transition for a view at the moment it is removed while marked (endViewTransition, which clears mTransitioningViews/mParent), or avoid marking descendants that React may still mutate during the animation.
Honest caveats
We do not have a minimal reproduction repository — this comes from a production app, so the report is the mechanism plus the evidence above rather than a runnable sample. Happy to test a patch against our case, which reproduces every time.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing removal-transition handling from screen.post { startRemovalTransition() } in NativeProxy.kt through Screen.kt, then inspect SurfaceMountingManager.kt and ReactClippingViewManager.kt around addViewAt. Done means the reported Fabric/Android re-parenting during an active removal transition no longer crashes, including the described non-clipping case; the report has no minimal reproduction or named test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin, react-native
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100